声明:
作者不是什么大佬,只是想写写算法,提高一下自己的内功。所以代码可能会非常凌乱,(大佬们就当个笑话看就可以了),但我会认真注释。
最后如果有路过的大佬,希望可以留下你们的建议和看法,谢谢!
198.打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
这道题我是看着题的解析写的,才知道什么叫做动态规划
知乎一个讲动态规划比较好的回答,建议看过之后,再去看看题,看看自己有没有想法
什么是动态规划
使用动态规划时,我认为的核心就用当前的最优解进行判断,并且不去询问最优解的得来(最优解的过程),只看最优解的值。
可能有的人回想,不管过程怎么求解呢?
解答:最优解的值是肯定要通过过程得来的,但是在我们抽象到每一碎片的时候,我们可以使用这个未知的最优解进行解答当前的最优解,而程序调用会不断深入未知的最优解一步一步的解析出来
我们先列出方程dp(i) = max(dp(i-1), dp(i-2)+a[i])
这个时候可能有的同学要提问了,这个逻辑在某种情况上看来是不成立的。
比如:[1,9,1,1,9,1]。
当数组为这种情况的时候我们发现最优解是偷两个,但是结合不偷的情况, 我们判断第4个位置上的1时,发现不满足不偷的情况,算法应该是有问题的。但是注意:
注意动态规划不用知道dp(k),第k步的时候不用去了解怎么实现到的,把之前的看作一个整体判断现在的就可以
,对应到这里那就是[1 9 1 1]虽然是10,但是新的最优解9是在[1 9 1 1 9]的情况下继续向左重新计算的结果,这个逻辑就是上述的方程。
// index是指当前的nums的 i值
// 整体是从右向左进行,但是程序实际的调用会通过递归深入到i=0开始判断
public int stole(int[] nums, int index) {
// 边界问题(可以化简)
if(nums==null || nums.length==0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
if(nums.length == 2){
return nums[0]>=nums[1] ? nums[0] : nums[1];
}
// 递归的动态规划的主体部分
// 说明递归到了最左位置,此时的最优解只能是nums[0]
if (index == 0 ) {
return nums[index];
}
// 说明递归到了第二位置,此时的最优解需要通过与第一位的大小进行判断
else if (index == 1) {
return nums[0] >= nums[1] ? nums[0] : nums[1];
}
// i>=2 时的普遍算法
else {
// dp方程的使用
// 偷 说明在i-2应该已经偷了,那么i的最优解就应该是i-2 + i 形成新的最优解
int yes = nums[index] + stole(nums, index - 2);
// 不偷 说明在i-1就已经偷了,那么i的最优解就是i-1的最优解
int no = stole(nums, index - 1);
// 返回i的最优解,会因为上面的递归依此进入进行计算
return yes >= no ? yes : no;
}
}
// 使用递归从右往左
public int rob(int[] nums) {
int i = stole(nums, nums.length - 1);
return i;
}
本算法的思路与上面的一致,只是在表达上有所不同。上一算法主要受到了,知乎例子的影响习惯性从大计算,但是拆分上面的递归逻辑,其实还是从i=0开始计算的。
核心还是:dp(i) = max(dp(i-1), dp(i-2)+a[i]),在计算第i像最优解的时候要考虑i-1和i-2处的最优解是什么
// 使用循环从左向右
public int rob(int[] nums) {
// 边界问题
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
if (nums.length == 2) {
return nums[0] > nums[1] ? nums[0] : nums[1];
}
// 循环主体
int n = nums.length;
// i-1的最优解
int last = nums[0] > nums[1] ? nums[0] : nums[1];
// i-2的最优解
int last_last =nums[0];
for (int i = 2; i < n; i++) {
// 计算不偷时的当前最优解 和 偷时当前的最优解
int temp = Math.max(last, last_last+nums[i]);
// 更改
last_last = last;
last = temp;
}
return last;
}