Description

所有房屋排成一個圓圈(第一間和最後一間相鄰)。相鄰房屋不能同時被搶。給定代表每間房屋金額的整數陣列 nums,求能搶到的最大金額。

Example:

Input: nums = [2,3,2] Output: 3

Intuition#

環形問題拆成兩個線性問題:搶第一間就不搶最後一間,反之亦然,取兩者最大值。

  • 因為首尾相鄰,不可能同時搶第一間和最後一間
  • 分成兩個子問題:nums[0..n-2]nums[1..n-1]
  • 每個子問題就是 House Robber I

Approaches#

1: Two-Pass DP Array#

  • 概念: 對兩個子陣列各跑一次 House Robber I 的 DP
  • 時間複雜度: O(n)
  • 空間複雜度: O(n)
class Solution {
    fun rob(nums: IntArray): Int {
        if (nums.size == 1) return nums[0]
        fun robRange(start: Int, end: Int): Int {
            val dp = IntArray(nums.size)
            dp[start] = nums[start]
            dp[start + 1] = maxOf(nums[start], nums[start + 1])
            for (i in start + 2..end) {
                dp[i] = maxOf(dp[i - 1], dp[i - 2] + nums[i])
            }
            return dp[end]
        }
        return maxOf(robRange(0, nums.size - 2), robRange(1, nums.size - 1))
    }
}

⭐2: Two-Pass Rolling Variables#

  • 概念: 同上但用滾動變數,每次只追蹤前兩個狀態
  • 時間複雜度: O(n)
  • 空間複雜度: O(1)
class Solution {
    fun rob(nums: IntArray): Int {
        if (nums.size == 1) return nums[0]
        fun robRange(start: Int, end: Int): Int {
            var prev2 = 0
            var prev1 = 0
            for (i in start..end) {
                val cur = maxOf(prev1, prev2 + nums[i])
                prev2 = prev1
                prev1 = cur
            }
            return prev1
        }
        return maxOf(robRange(0, nums.size - 2), robRange(1, nums.size - 1))
    }
}

🔑 Takeaways#

  • Pattern: 環形 DP — 把環形拆成兩個線性子問題,分別求解再取最大值
  • 關鍵技巧: 環形約束的處理套路:首尾互斥,分兩種情況討論