做动态规划题目肯定绕不开“打家劫舍”,这是一个经典的动态规划题目。下面,就让我带你“打家劫舍,劫富济贫”。。。
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例
示例 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
题解
这种题目如果做多了,基本上就能记住套路了。分析的方法就是从易到难,通过简单推演,发现规律,再通过递推公式来求解最大可偷金额(后面简称最大金额)。
我们把这一连串的房子及对应的现金存入一个数组中,然后想办法从这个规则中找到规律。假如这个数组a只有一个值,那可偷的最大金额就是a[0];假如这个数组a有两个值,那最大金额就是Math.max(a[0],a[1])。到这里,通过简单的推理我们就已经知道了a.length <= 2时的最大金额。
再来,如果a有三个值呢?这其实取决于前两个值的最大值的位置,如果最大值在第一位,那就可以加第三个值求得最大金额。如果前面两个的最大金额在第二位,那此时就无法再加上这第三个值,但就这三个值来说,仅仅是中间这个'最大值'还不能确定是最大金额,这时还需要去比较a[0]+a[2]与a[1]的大小。比如,[2,3,2],这就是前面提到的,前面两个3最大,但是2+2更大。
基于上面的推理,我们现在可以构建动态公式了。可以想到,数组a[i]之前的最大金额dp[i],需要面对两种情况:dp[i-1] === dp[i-2]及dp[i-1] !== dp[i-2]。如果相等,说明a[i-1]没有参与计算dp[i-1]的值,那按照规则在计算dp[i]时,a[i]就可以偷了,多一毛是一毛;如果不相等,问题就复杂了,很显然a[i-1]参与计算了dp[i-1],那对dp[i]来说,a[i]无法再偷,但此时就得比较下dp[i-1]与dp[i-2] + a[i]的大小了,比较结果就是dp[i]。ok,动态公式已经完成了,下面看代码吧!
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
var l = nums.length
var dp = new Array(l)
dp[0] = nums[0]
dp[1] = nums[1] > nums[0] ? nums[1] : dp[0]
for(var i = 2;i<l;i++){
dp[i] = dp[i-1]===dp[i-2]? dp[i-2]+nums[i] : Math.max(dp[i-2]+nums[i],dp[i-1])
}
return dp[l-1]
};
需要注意的是递推是从i=2开始的,其实你从i=3开始应该也行,可以试下。
复杂度分析
时间复杂度:O(n); 空间复杂度:O(1),常量级的空间占用。