198. 打家劫舍
题目来源
题目分析
在这个问题中,小偷需要在一条街上偷窃房屋,而不能连续偷窃相邻的房屋,否则会触发警报。给定一个代表每个房屋存放金额的非负整数数组,计算小偷能够在一夜之内偷窃到的最高金额。
题目难度
- 难度:简单
题目标签
- 标签:动态规划
题目限制
1 <= nums.length <= 1000
0 <= nums[i] <= 400
解题思路
为了解决这个问题,我们可以使用动态规划。动态规划的思想是通过定义一个 dp
数组,记录在偷窃前 i
间房屋时能够获取到的最高金额。我们可以逐步计算,最终得到偷窃整个街道的最大收益。
核心算法步骤
- 动态规划:
- 定义
dp[i]
表示偷窃前i
间房屋时,能获取的最高金额。 - 状态转移方程:
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
- 即第
i
间房屋的最高金额为两种情况中的最大值:- 不偷第
i
间房屋时的最高金额dp[i - 1]
。 - 偷第
i
间房屋时的最高金额dp[i - 2] + nums[i]
。
- 不偷第
- 初始化边界条件:
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
- 最终结果为
dp[nums.length - 1]
,表示偷窃所有房屋时的最高金额。
- 定义
代码实现
以下是求解房屋偷窃问题的 Java 代码:
/**
* 198. 打家劫舍
* @param nums 房屋存放金额的非负整数数组
* @return 偷窃到的最高金额
*/
public static int rob(int[] nums) {
int[] dp = new int[nums.length + 2];
for (int i = 0; i < nums.length; i++) {
dp[i + 2] = Math.max(dp[i + 1], dp[i] + nums[i]);
}
return dp[nums.length + 1];
}
//可读性更高的版本
public static int rob1(int[] nums) {
if (nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
代码解读
-
动态规划逻辑:
dp[i + 2] = Math.max(dp[i + 1], dp[i] + nums[i])
表示选择偷窃当前房屋i
与不偷的最大值,并且下标从i + 2
开始是为了便于处理状态转移的边界条件。- 通过
dp[i + 2]
的定义,可以简化边界条件,将其融入循环中,相当于将dp数组相比原来全部向后移了2位。
-
路径管理:
- 使用一个
dp
数组来记录每个房屋位置的最高金额,最后返回dp[nums.length + 1]
,即整个街道的最大偷窃金额。
- 使用一个
性能分析
- 时间复杂度:
O(n)
,遍历数组一次,每个房屋只计算一次最大金额。 - 空间复杂度:
O(n)
,由于使用了一个dp
数组来存储每个房屋位置的最高金额。
测试用例
你可以使用以下测试用例来验证代码的正确性:
int[] nums = {2, 7, 9, 3, 1};
int result = rob(nums);
System.out.println(result);
// 输出: 12 (偷窃 2 + 9 + 1)
int[] nums2 = {1, 2, 3, 1};
int result2 = rob(nums2);
System.out.println(result2);
// 输出: 4 (偷窃 1 + 3)
扩展讨论
优化写法
在空间复杂度上,可以进一步优化为 O(1)
,通过两个变量来记录当前状态和前一状态,从而省去 dp
数组的空间。
public int rob(int[] nums) {
if (nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int dp0 = nums[0];
int dp1 = Math.max(nums[0], nums[1]);
for (int i=2;i<nums.length;i++){
int dp2=Math.max(dp1, dp0+nums[i]);
dp0=dp1;
dp1=dp2;
}
return dp1;
}
其他实现
除了动态规划,还可以通过递归加记忆化搜索的方式来解决这个问题,不过由于记忆化的重复计算,性能上不如动态规划高效。
总结
这道题目通过动态规划的方式解决了房屋偷窃问题。动态规划的核心在于状态的转移和边界条件的处理。这种方式在类似的数组问题中有广泛的应用,是理解动态规划思想的重要题目之一。