题目
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入: nums = [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: nums = [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 3:
输入: nums = [0]
输出: 0
提示:
1 <= nums.length <= 1000 <= nums[i] <= 1000
分治思路
在 198.打家劫舍 - 从分治到动态规划 中分析了如何求偷 0 ~ n 间房子,并且不能连续偷两个房子的最大金额.而这题在 198 题的基础上增加了一个条件,最后一间房子和第一间房子是相连的.这样第一间和最后一间不能同时被偷.就这个条件进行分解: 可以分别求出偷第 1 ~ n-1 和 2 ~ n 间房子的最大金额,然后取其中的较大者即为偷 1 ~ n 间房子的最大金额.
用另一种状态解 198.打家劫舍
在 198.打家劫舍 - 从分治到动态规划 的解题思路是分别去计算偷与不偷第 i 间这样来定义状态,在上篇文章结尾透露了可以使用另外一种状态来解题,这里就用上了,这里定义偷第 i 间房子能获得的最大金额作为状态,这样只要一维的状态,实现起来会更简洁一些.
function rob(nums: number[]): number {
const dp: number[] = []
for (let i = 0; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] ?? 0, (dp[i - 2] ?? 0) + nums[i])
}
return dp[nums.length - 1]
}
- 时间复杂度:
- 空间复杂度:
实现 213.打家劫舍-ii
按照之前的思路,分别求 1 ~ n-1 和 2 ~ n 的最大金额,然后取其中较大者.
其中 dp1 表示偷 1 ~ n-1 间房子的状态,dp2 表示偷 2 ~ n 间房子的状态
需要注意这里要处理一个特殊的边界,当只有一间房子的时候,能偷的最大金额就是偷唯一的那一间房子.
另外对于小于 0 的索引,使用 ??(空值合并运算符) 来处理为 0,比如偷第一间房子的时候,这时 i-1 和 i-2 都是不存在的,也就相当于是偷到 0 元.
当然还有其他的处理方法,比如在 nums 之前加两个 0 的元素,或者额外去判断 i-1 和 i-2 是否小于 0,或者在 dp 的数组中往后偏移两位.只是我个人感觉直接用 ?? 会更简洁一些.
function rob(nums: number[]): number {
const n = nums.length
if (n === 1) return nums[0]
const dp1: number[] = []
for (let i = 0; i < n - 1; i++) {
dp1[i] = Math.max(dp1[i - 1] ?? 0, (dp1[i - 2] ?? 0) + nums[i])
}
const dp2: number[] = []
for (let i = 1; i < n; i++) {
dp2[i] = Math.max(dp2[i - 1] ?? 0, (dp2[i - 2] ?? 0) + nums[i])
}
return Math.max(dp1[dp1.length - 1], dp2[dp2.length - 1])
}
- 时间复杂度:
- 空间复杂度:
优化代码
我们可以将两遍循环都写到一次循环内,这样可以省掉一次循环,看起来会简洁很多
这里有一个小处理是直接将
dp1[0]初始化为nums[0],将dp2[0]初始化为 0,这样可以省去对只有一间房子的判断.这时遍历可以直接从第二间房子开始,对应到数组中就是索引 i 从 1 开始遍历.
function rob(nums: number[]): number {
const n = nums.length
const dp1: number[] = [nums[0]]
const dp2: number[] = [0]
for (let i = 1; i < n; i++) {
if (i < n - 1) dp1[i] = Math.max(dp1[i - 1], (dp1[i - 2] ?? 0) + nums[i])
dp2[i] = Math.max(dp2[i - 1], (dp2[i - 2] ?? 0) + nums[i])
}
return Math.max(dp1[dp1.length - 1], dp2[dp2.length - 1])
}
- 时间复杂度:
- 空间复杂度:
空间优化
我们发现 dp1[i] 只跟 dp1[i-1] 以及 dp1[i-2] 有关,这时可以用两个变量来代替 dp 数组,进行空间优化,dp2 同理
function rob(nums: number[]): number {
const n = nums.length
let [cur1, pre1] = [nums[0], 0]
let [cur2, pre2] = [0, 0]
for (let i = 1; i < n; i++) {
if (i < n - 1) [cur1, pre1] = [Math.max(cur1, pre1 + nums[i]), cur1]
;[cur2, pre2] = [Math.max(cur2, pre2 + nums[i]), cur2]
}
return Math.max(cur1, cur2)
}
- 时间复杂度:
- 空间复杂度:
打家劫舍系列: