题目介绍
力扣198题:https://leetcode-cn.com/problems/house-robber/
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
分析
由于不能偷窃连续的房屋,我们自然想到,隔一个偷一间显然是一个不错的选择。那是不是,直接计算所有奇数项的和,以及所有偶数项的和,取最大值就可以了呢?并没有这么简单。例如,如果是[2,7,1,3,9],很明显,偷2,1,9或者7,3都不是最佳选择,偷7,9才是。
这里的关键是,对于三个连续的房屋2,7,1,由于跟后面的9都隔开了,所以我们可以选择偷2,1,也可以直接选择偷7。这就需要分情况讨论了。所以我们发现,从最后往前倒推,最后一间屋n,有偷和不偷两种选择:
- 如果偷,那么前一间屋n-1一定没有偷,我们考虑n-2之前的最优选择,加上n就可以了;
- 如果不偷,那么n-1之前的最优选择,就是当前的最优选择。
所以,这明显是一个动态规划的问题。
动态规划实现
我们可以将前n个房屋能偷到的最大金额,保存到状态数组dp。前i个房屋能够偷到的最大金额,就是dp[i]。
可以得到状态转移方程:
代码演示如下:
public class HouseRobber {
// 动态规划
public int rob(int[] nums) {
int n = nums.length;
if (nums == null || n == 0) return 0;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = nums[0];
// 遍历状态,依次转移
for (int i = 2; i <= n; i++){
dp[i] = Math.max( dp[i-1], dp[i-2] + nums[i-1] );
}
return dp[n];
}
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
- 空间复杂度:O(n)。使用数组dp存储状态,长度为n+1。
空间优化
上述方法使用了数组存储结果。
我们通过状态方程可以发现,每间房屋的最高总金额,只和该房屋的前两间房屋的最高总金额相关。因此只要存储之前两间房屋的最高金额就可以了。
代码如下:
// 动态规划空间优化
public int rob(int[] nums) {
int n = nums.length;
if (nums == null || n == 0) return 0;
int pre2 = 0;
int pre1 = nums[0];
// 遍历状态,依次转移
for (int i = 1; i < n; i++){
int curr = Math.max(pre1, pre2 + nums[i]);
pre2 = pre1;
pre1 = curr;
}
return pre1;
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
- 空间复杂度:O(1)。使用滚动数组,只存储前两间房屋的最高总金额,而不需要存储整个数组的结果,因此空间复杂度是 O(1)。