问题分析
题目是 打家劫舍(House Robber),给定一个整数数组 nums,其中 nums[i] 代表第 i 间房子里的现金。
• 你不能 连续 偷两间相邻的房子,否则会触发警报。
• 目标是 求能够偷到的最大金额。
解题思路
使用 动态规划(Dynamic Programming, DP) 解决:
1. 定义状态 dp[i]:
• dp[i] 代表在 前 i 个房子 中,能偷到的最大金额。
2. 状态转移方程:
• 对于第 i 个房子,有 两种选择:
• 不偷 第 i 间房子 → 保持 dp[i-1]
• 偷 第 i 间房子 → 获得 nums[i-1],并加上 dp[i-2](因为不能偷相邻房子)
• 取最大值:
3. 初始化:
• 只有一间房子 nums[0],最大金额就是 nums[0]。
• dp[1] = nums[0]
• dp[2] = max(nums[0], nums[1])(两间房子,偷金额较大的那间)
4. 最终答案:
• dp[n] 代表能偷的最大金额。
代码解析
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0]; // 只有一间房子,直接返回
vector<int> dp(nums.size() + 1); // dp 数组
dp[1] = nums[0]; // 偷第一间房子
dp[2] = max(nums[0], nums[1]); // 偷第一间或第二间的较大值
for(int i = 3; i <= nums.size(); i++) {
dp[i] = max(dp[i - 1], nums[i - 1] + dp[i - 2]); // 状态转移方程
}
return dp[nums.size()]; // 返回最后一个房子的最大金额
}
};
运行步骤
假设输入:
nums = {2, 7, 9, 3, 1}
Step 1: 初始化
房子编号 | 现金 nums[i] | dp[i] 计算方式 | 结果 |
---|---|---|---|
1 | 2 | dp[1] = nums[0] | 2 |
2 | 7 | dp[2] = max(nums[0], nums[1]) = max(2, 7) | 7 |
Step 2: 动态规划填表
房子编号 i | nums[i-1] | 计算 dp[i] | 结果 |
---|---|---|---|
3 | 9 | dp[3] = max(dp[2], dp[1] + nums[2]) = max(7, 2 + 9) = 11 | 11 |
4 | 3 | dp[4] = max(dp[3], dp[2] + nums[3]) = max(11, 7 + 3) = 11 | 11 |
5 | 1 | dp[5] = max(dp[4], dp[3] + nums[4]) = max(11, 11 + 1) = 12 | 12 |
Step 3: 最终结果
dp[5] = 12,表示最大可偷金额为 12。
时间复杂度分析
• dp[i] 依赖于 dp[i-1] 和 dp[i-2],遍历 nums 一次,时间复杂度为:
• dp 数组占 O(n) 额外空间,优化后可用 O(1) 额外空间(优化见下)。
优化
我们只需要 dp[i-1] 和 dp[i-2],可以用 两个变量 代替 dp 数组,降低空间复杂度:
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int prev2 = nums[0]; // 相当于 dp[i-2]
int prev1 = max(nums[0], nums[1]); // 相当于 dp[i-1]
int curr = prev1;
for(int i = 2; i < nums.size(); i++) {
curr = max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
}
return curr;
}
};
• 时间复杂度: O(n)(遍历 nums 一次)
• 空间复杂度: O(1)(只使用三个变量)
总结
1. 状态定义: dp[i] 为前 i 个房子的最大可偷金额。
2. 状态转移方程: dp[i] = max(dp[i-1], dp[i-2] + nums[i-1])。
3. 时间复杂度 O(n),空间复杂度可优化至 O(1)。
4. 滚动数组优化,只用两个变量存 dp[i-1] 和 dp[i-2],节省空间。 🚀