#213

House Robber II

Medium
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 Memoization (Top-Down) — O(n) / O(n)
  • 概念: 對兩個子區間 [0, n-2][1, n-1] 各跑一次帶 memo 的遞迴
  • 時間複雜度: O(n) - 每個狀態只算一次
  • 空間複雜度: O(n) - memo 陣列 + 遞迴堆疊
class Solution {
    fun rob(nums: IntArray): Int {
        if (nums.size == 1) return nums[0]
        fun robRange(lo: Int, hi: Int): Int {
            val memo = IntArray(nums.size) { -1 }
            fun dp(i: Int): Int {
                if (i > hi) return 0
                if (memo[i] != -1) return memo[i]
                memo[i] = maxOf(nums[i] + dp(i + 2), dp(i + 1))
                return memo[i]
            }
            return dp(lo)
        }
        return maxOf(robRange(0, nums.size - 2), robRange(1, nums.size - 1))
    }
}
  1. Two-Pass DP Array — O(n) / O(n)
  • 概念: 對兩個子陣列各跑一次 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))
    }
}
⭐ 3. Two-Pass Rolling Variables — O(n) / O(1)
  • 概念: 同上但用滾動變數,每次只追蹤前兩個狀態
  • 時間複雜度: 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 — 把環形拆成兩個線性子問題,分別求解再取最大值
  • 關鍵技巧: 環形約束的處理套路:首尾互斥,分兩種情況討論