文章目录
- 1. 题目来源
- 2. 题目解析
1. 题目来源
链接:198. 打家劫舍
前置:
- [每日一题] 146. 打家劫舍(数组、动态规划、巧妙解法)
2. 题目解析
记忆化搜索可以处理,是自顶向下进行枚举的,属于 递归。
动态规划,属于 递推。
思路:dfs 记忆化搜索
- dfs 搜索前
i
个房子所能获取的最大金额。 - 对于
i
来说,有两种情况,选 或者 不选。 - 选了
i
,那么意味着状态是由 dfs(i-2) 转移过来的。 - 不选
i
那么意味着状态是由 dfs(i-1) 转移过来的。 - 也就是说,每一个位置都需要考虑 选、不选 的问题。
- 此时时间复杂度即为 2 n 2^n 2n
- 其实能发现,有很多重复计算的状态,所以可以将其计算的结果进行记忆化,然后再次进入该状态时,直接返回结果即可。
- 借用一下灵神的图:
- 此时时间复杂度就变成了 O ( n ) O(n) O(n)。因为有 n 个状态,但每个状态只会被计算一次。
思路:dp
- 很简单的 dp 哈。
- dp[i] 表示前 i 个房屋的最大金额。
- 状态转移:dp[i]=max(dp[i-1], dp[i-2]+nums[i]);
- 注意这里有 i-1、i-2,需要初始化 dp[0]、dp[1] 这两个状态 即可。
- 额外注意 nums 值为 1 的情况,此时没有办法做 i-2 的转移,直接返回结果即可。
综上,比对两者做法:
- 记忆化搜索:通过结果过去解决问题。
- dp:通过 递推的方式解决问题。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
代码:
记忆化搜索:
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> memo(n, -1); // -1 表示没有计算过
// dfs(i) 表示从 nums[0] 到 nums[i] 最多能偷多少
auto dfs = [&](auto&& dfs, int i) -> int {
if (i < 0) return 0; // 递归边界(没有房子)
if (memo[i] != -1) return memo[i]; // 之前计算过
return memo[i] = max(dfs(dfs, i - 1), dfs(dfs, i - 2) + nums[i]);
};
return dfs(dfs, n - 1); // 从最后一个房子开始思考
}
};
dp
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 1) return nums[0];
vector<int> f(n);
f[0] = nums[0];
f[1] = max(nums[0], nums[1]);
for (int i = 2; i < n; i ++ ) f[i] = max(f[i - 1], f[i - 2] + nums[i]);
return f[n - 1];
}
};