JimmyZhan的Java算法学习日记-LeetCode_198
题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
示例 2:
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
一、动态规划
1、动态规划思路:
乍一看, “取或者不取”,“怎么取得到最大收益”,诸如此类的,优先考虑动态规划思想。
好,那么你来说说动态规划怎么搞嘛。过几天写个动态规划的专题。
动态规划是求解决策过程最优化的过程,把一个大问题(母问题),分解成一个一个的小问题(子问题),从一个最小的子问题开始,求得最优解,然后将子问题的边界移动一步,变成一个稍微大一点的子问题,此时,该子问题的最优解,就依赖于前一个子问题的最优解。依此法,不断移动子问题的边界,最后还原成母问题,求得最优解。
2、就拿本体作解释:
情况一
本题中最小的子问题就是当nums数组中仅有一个元素的时候,也就是我们只有一家可以偷,那么最优解就毋庸置疑,偷他!
return 甲家财产;
情况二
.然后移动一步问题边界,此时的子问题就变成了:现在有两家,我怎么偷收益最大?
别忘了题目说了,不能连偷相邻的两家。所以这两家我们无法兼得,最大收益的决策即是:谁家钱多偷谁的。return max(甲家财产,乙家财产);
情况三、四、五…n
一步一步移动子问题边界,并求得当前的最优解即可。
3、边界条件:
好,现在注意了,我们可以发现第一种和第二种情况下,最大收益是不依赖于任何子问题的,因为他们两种情况是最小子问题,所以我们可以写出边界条件,如下:
我们用 dp[nums.length] 来存放每个子问题的最优解
上面一行表示第一种情况
下面一行表示第二种情况
2、状态转移方程:
我稍微解释一下:
因为第i家和第i-1家不兼容,所以max()函数的第一个参数是 dp[i-2] 不是 dp[i-1]
所以把第i-1家直接干掉
二、上代码
1)常规解法、
public int rob(int[] nums) {
int i;
int length = nums.length;
if (length == 0){
return 0;
} else if(length == 1){
return nums[0];
}
int[] dp = new int[length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (i=2; i<length; i++){
dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
}
return dp[length-1];
}
执行信息如下:
这内存消耗。。。无语了家人们,尝试优化一下,多加个判断条件试试,if肯定是比调用max()函数来得快的
2)优化了一次(其实不痛不痒)、
public int rob(int[] nums) {
int i;
int length = nums.length;
if (length == 0){
return 0;
} else if(length == 1){
return nums[0];
} else if(length == 2) {
return Math.max(nums[0], nums[1]);
}
int[] dp = new int[length];
dp[0] = nums[0];//就一家可以偷
dp[1] = Math.max(nums[0], nums[1]);//就两家可以偷的时候取一个最大即可
for (i=2; i<length; i++){
dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
//dp[i-2]而不是dp[i-1],因为第i个时不能用前i-1个,因为不知道第i-1个有没有被偷,为了确保第i-1个没被偷,用dp[i-2]
}
return dp[length-1];
}
这确实效果不大,执行信息如下。。。↓↓↓
看得出来,不太彳亍。于是我改为使用从题解里新学来的另一种技术:
三、滚动数组
1.滚动数组思路
因为每间房屋的最高金额只与该房屋的前两间房屋的最高总金额相关
因此采用滚动数组
在每个子问题中只需要存储前两个房屋的最高总金额(即: dp[i-2] 和 dp[i-1] )
这里用两个变量first和second存储
2.代码如下
public int rob(int[] nums){
int i;
int length = nums.length;
if (length == 0){
return 0;
} else if(length == 1){
return nums[0];
}
int first = nums[0];
int second = Math.max(nums[0], nums[1]);
for (i=2; i<length; i++){
int temp = second;
second = Math.max(first+nums[i], second);
first = temp;
}
return second;
}
3.执行信息如下
立竿见影了兄弟们,这一下就从30%冲到68%了
但是你可能会问内存消耗也没减多少啊
解释一下,这里数组比较小,所以表面上看不出什么很大的变化,但是我给你分析分析:
优化之前的代码时间复杂度是O(n),空间复杂度也是O(n) —— 一维数组。
优化后时间复杂度依然是O(n),而没有数组,只有两个变量,所以空间复杂度是一个常量级,即O(1)
你品,你细品。
总结
“ 牺牲了时间换取空间,我们让它们像西西弗斯推动的巨石一样滚动起来 ”
——占吉米斯基