213.打家劫舍-ii - 依旧从分治开始

705 阅读2分钟

题目

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 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 <= 100
  • 0 <= nums[i] <= 1000

分治思路

198.打家劫舍 - 从分治到动态规划 中分析了如何求偷 0 ~ n 间房子,并且不能连续偷两个房子的最大金额.而这题在 198 题的基础上增加了一个条件,最后一间房子和第一间房子是相连的.这样第一间和最后一间不能同时被偷.就这个条件进行分解: 可以分别求出偷第 1 ~ n-12 ~ 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]
}
  • 时间复杂度: O(n)O(n)
  • 空间复杂度: O(n)O(n)

实现 213.打家劫舍-ii

按照之前的思路,分别求 1 ~ n-12 ~ 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])
}
  • 时间复杂度: O(n)O(n)
  • 空间复杂度: O(n)O(n)

优化代码

我们可以将两遍循环都写到一次循环内,这样可以省掉一次循环,看起来会简洁很多

这里有一个小处理是直接将 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])
}
  • 时间复杂度: O(n)O(n)
  • 空间复杂度: O(n)O(n)

空间优化

我们发现 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)
}
  • 时间复杂度: O(n)O(n)
  • 空间复杂度: O(1)O(1)

打家劫舍系列: