打家劫舍
题目要求:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
问题分析
- 你有一栋房子,每间房子都有一定数额nums[i]。
- 相邻房子不能同时偷。
- 问题等价于:选择一组不相邻的数字,使它们的和最大。
动态规划思路
- 定义状态:
dp[i]表示偷到第i一个房子时能获得的最大金额。
- 状态转移方程:
- 偷第i套房子:dp[i] = dp[i-2] + nums[i]
- 不偷第i套房子:dp[i] = dp[i-1]
- 取两者最大:dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
- 初始条件:
- dp[0] = nums[0]
- dp[1] = Math.max(nums[0], nums[1])
- 最终答案:
- dp[n-1],即偷到最后一间房的最大金额。
Java代码实现如下:
public class HouseRobber {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int n = nums.length;
if (n == 1) return nums[0];
// dp[i] 表示偷到第 i 个房子能获得的最大金额
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[n-1];
}
public static void main(String[] args) {
HouseRobber hr = new HouseRobber();
int[] houses = {2, 7, 9, 3, 1};
System.out.println("最大偷窃金额: " + hr.rob(houses));
}
}
优化空间
注意dp[i]只依赖dp[i-1]和dp[i-2],所以可以用两个变量替代数组,这个方法空间复杂度O(1)
int prev1 = Math,max(nums[0],nums[1]);
int prev2 = nums[0];
for(int i = 2;i<nums.length;i++){
int current = Math.max(prev1,prev2+nums[i]);
prev2 = prev1;
prev1 = current;
}
return prev1;
对比一下更容易理解
原始DP
int[] dp = new int[n];
空间会随着n增长:
所以空间复杂度 = O(n)
优化后
int prev1;
int prev2;
无论数组多大,变量数量始终固定为2个,所以空间不会随着n增长。
所以空间复杂度 = O(1)
1. 为什么必须用DP?(核心原因)
这道题的关键不是“当前选不选最大”,而是:
“当前选择会影响后面所有选择”
状态依赖是:
dp[i]依赖dp[i-1]和dp[i-2]
也就是说:
不是一步决策,而是“带历史约束的最优决策”
2. DP的本质理解
每个房子都有两个状态:
偷/不偷
所以不是选"最大值",而是比较:
- 偷当前 = dp[i-2]+nums[i]
- 不偷当前 = dp[i-1]
这是"带记忆的决策",不是单步最优
3. 为什么这道题不能贪心?(总结核心)
贪心成立需要两个条件:
贪心选择性质:
"局部最优一定能导出全局最优"
无后效性:
"当前选择不影响未来最优结构"
但本题:
- 当前选择会影响后面
- 局部最优无法保证全局最优
- 一句话总结
- 这道题不能用贪心,因为"选当前最大"会破坏后续最优结构;
- 必须用DP来记录"选或不选"的全局最优状态
浙公网安备 33010602011771号