198. 打家劫舍
难度 中等
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
题解
第一眼看到这道题,完全不知道怎么下手,刚开始都是想暴力枚举,但是觉得肯定超时。想了好久都没有想法就看官方题解去了,使用的是动态规划。看完官方题解之后才知道怎么做,现在就按照官方题解给出自己的解答,不然这道题就白做了。
解法一:
动态规划四部曲:
确定状态
- 研究最优策略的最后一步
- 转化为子问题
状态转移方程
- 根据子问题定义直接得到
初始条件和边界情况
- 细心,考虑周全
确定计算顺序
- 利用之前的计算结果
-
确定状态
- 如果到最后一步,我们如何取得最大值呢,那么只可能取两种可能。
- 第一种,上一个房子不取,取上上个房子,再加上当前房子
- 第二种,取了上个房子,当前房子不能取
- 子问题
- 那么最后一步就与房间数n-1和n-2的问题有关,只要求出n-1和n-2的问题就可以求解n的问题
- 如果到最后一步,我们如何取得最大值呢,那么只可能取两种可能。
-
状态转移方程(最重要)
-
上面的文字描述转为方程的话
dp(n) = max(dp(n-1), dp(n-2) + value[n])
-
-
初始条件和边界情况
- 那问题规模(即房间数量)减小到0,1,2这些就容易解决了
- n=0,返回0
- n=1,返回value[0]
- n=2,返回max(value[0], value[1])
- 那问题规模(即房间数量)减小到0,1,2这些就容易解决了
-
确定计算顺序
-
正向计算
从0到n,迭代计算
-
反向计算
从n开始,递归调用n-1和n-2
-
class Solution {
public int rob(int[] nums) {
int len = nums.length;//获取数组长度
if (nums == null || nums.length == 0) {//数组为空
return 0;
}
if(len == 1){//长度为1,只有一间房,直接返回
return nums[0];
}
int[] ans = new int[len];//dp数组
ans[0] = nums[0];//初始化边界
ans[1] = Math.max(nums[0], nums[1]);//初始化边界
for(int i = 2; i < len; i++){//
ans[i] = Math.max(ans[i-2] + nums[i], ans[i-1]);//状态转移方程
}
return ans[len-1];
}
}
解法二:
其实每次转移的过程,只用到上一个房子和上上个房子而已,其他的没有用到,开辟一个长度len的数组有点浪费,这里可以采用滚动数组来优化空间。那怎么优化呢,其实就是不要长度为len的数组,只采用两个数保存上一个房子和上上个房子。
class Solution {
public int rob(int[] nums) {
int len = nums.length;//获取数组长度
if (nums == null || nums.length == 0) {//数组为空
return 0;
}
if(len == 1){//长度为1,只有一间房,直接返回
return nums[0];
}
int first = nums[0];//记录上个房子
int second = Math.max(nums[0], nums[1]);//记录上上个房子
for(int i = 2; i < len; i++){
int temp = second;//临时遍历,用来保存上个房子,待second修改后,可以更新first
second = Math.max(first + nums[i], second);//转态转移方程
first = temp;//更新first
}
return second;
}
}