这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
打家劫舍(题号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 <= 1000 <= nums[i] <= 400
链接
解释
这题啊,这题是经典DP。
先看看题目,题意简化一下就是:给你一个数组,你不能取相邻的元素,求可以取到数字的最大值。
DP的思路就很简单了,搞一个DP数组来记录在每个元素上可以取到的最大值。也就是说dp[i]是在当前位置上的最大数字了。
那怎么求到dp[i]呢?dp[i]其实有两种可能:
- 不取前面一个数字,去前面两位的数字,加上当前的数字,也就是
dp[i - 2] + nums[i] - 取前面一个数字,那自然就不能取当前元素了,也就是
d[i - 1]
所以DP公式就是:
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1])
取二者的最大值即可,DP数组的最后一个元素就是在整个数组中可以取到的最大值。
让我们再思考一下,根据👆的DP公式,可以发现dp[i]之和前两个元素相同,那是不是可以不用数组了,只用两个变量来存储前两位的值,每次迭代更新这两个变量,那么也可以拿到最后的结果。
而且空间复杂度从O(n)干到了O(1),优化了一些。
自己的答案(DP)
经典DP的操作和解释中说的一样👇:
var rob = function(nums) {
const dp = new Array(nums.length)
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
for (let i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1])
}
return dp[nums.length - 1]
};
注意这里的dp[0]和dp[1]的初始化赋值,因为dp[i]和前两个数字相关,那么在开始循环之前,得先有两个初始化的值,否则有可能会取不到前面两位的值。
后续的循环就是不断更新DP数组的过程,如此取下去,DP数组的最后一个元素就是我们需要返回的值。
自己的答案(DP+降维)
解释中说过,完全可以用两个变量来代替数组👇:
var rob = function(nums) {
let before1 = Math.max(nums[0], nums[1]) || Number.MIN_SAFE_INTEGER
let before2 = nums[0]
for (let i = 2; i < nums.length; i++) {
[before1, before2] = [Math.max(before2 + nums[i], before1), before1]
}
return Math.max(before1, before2)
};
这里用before1代替前一位的变量,用before2代替前两位的变量,在循环的过程中也是不断更新before1和before2即可。
还有需要注意的地方是before1的赋值,因为如果数组的长度为1,before1取到的值就是NaN了。
尴尬的地方来了,Math.max(NaN, 0)的值竟然是NaN,你敢信?这样最后的返回值就是NaN了,多尴尬,所以这里用Number.MIN_SAFE_INTEGER来表示不存在的before1,用-Infinity也可以,随意。
最后的返回值也得在before1和before2中取值,因为数组有的长度如果是2,并且第二个元素比第一个元素大,那么before2就是答案,如果数组的长度为1,那么before1就是答案,所以得取两种中较大的那个值。
奇思妙想
其实还发现了一种很有趣的答案:

写这个答案的老哥真的太可爱了~
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇