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 。
对于数组中的每一个位置(每一间房),我们都有两种选择:进入这间房/不进入这间房。
如果进入这间房:则和这间房相邻的房间必然不能进入
如果没有进入这间房: 则这间房相邻的房间可以随意进入
当我们遍历完整个数组的时候,我们才能知道所有房间的情况,包括我们该选择哪个房间,所以我们要从数组末尾开始进入递归。
当确认了最后一个房间的选择情况,我们就可以向前确认其他房间的情况,即从数组的末端向前方递归。
递归出口:
- 如果只有一个房间让小偷去偷,小偷的最大收获只能是这个房间的内容
- 如果有两个房间给小偷,小偷的最大收获是这两个房间中价值最高的那一个
设计递归函数:
在递归函数中我们需要传入数组(所有房间的选择),和一个变量 i(表示当前我们在考虑的是哪一间房子)
假设我们要传入的房子数组是 int[ ] houses = {2,7,9,3,1} ,则对于递归函数:process( int[ ] houses, int i ),
当i == 0的时候,应该返回houses[0],
当i == 1的时候,应该返回houses[0] 和hosues[1]中最大的那个
当i为其他值的时候,我们要考虑当前i表示的房子要不要去偷,如果偷,那么上一间房子就不能去偷了:返回 process(houses, i-2) + houses[i]
如果不去偷,那么上一间房子就可以去偷:返回process(houses, i-1)
递归函数如下:
public int process(int[] nums, int i){
if(i == 0){
return nums[0];
}
if(i == 1){
return Math.max(nums[0], nums[1]);
}
int A = process(nums, i-1); //不偷当前房子
int B = process(nums, i-2)+nums[i]; //偷当前房子
return Math.max(A, B); //选择两者中最大的值
}
递归的复杂度为O(2n),递归的弊端
当我们来到第5间房间的时候,如果偷第5间房:需要计算第3间房的情况;如果不偷第5间房:需要计算第4间房的情况
当我们来到第4间房的时候,如果去偷第4间房:需要计算第2间房的情况;如果不偷第4间房:需要计算第3间房的情况
综上可见:在数组的遍历过程中,第3间房的情况被计算了两次,如果用一种方法将第3间房的值保存起来,就可以加速递归的过程
动态规划的解法:
- 准备一个dp数组dp[ ],数组的长度即为房间的数量(因为我们不存在一个都不偷的情况)
- 我们要求的最后的结果即为dp[dp.length-1],即走完所有的房子之后,我们才能知道最大收益可以是多少
- 当我们关注最后一个房间的情况(即dp[dp.length-1])的时候,我们还需要考虑它的前一位和前两位的值
- 最终的迭代出口在数组的第0位和第1位
- dp[0] = houses[0],dp[1] = Math.max(houses[0], houses[1])
- 从第二位开始,每一位的值 dp[ i ] 都是:Math.max ( dp[ i - 1] , dp[ i - 2 ] + houses[ i ] )
- 从第2位开始向后迭代,我们便可以将这个数组填满,取最后一位的值返回即可
public int rob(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
int[] dp = new int[nums.length]; //动态规划迭代法需要的数组
dp[0] = nums[0]; //先将第0位和第1位填好
dp[1] = Math.max(nums[0], nums[1]);
for(int i = 2; i<nums.length; i++){
int A = dp[i-1];
int B = dp[i-2]+nums[i];
dp[i] = Math.max(A,B);
}
return dp[nums.length-1];
}