150NeetCode 150

102.House Robber II

MediumBlind 751-D Dynamic Programming ยท 4/12

Problem

Same rules as House Robber โ€” you cannot rob two adjacent houses โ€” except the houses are arranged in a circle: the first house nums[0] and the last house nums[n - 1] are neighbours.

Return the maximum amount you can rob without alerting the police.

Examples

Input:  nums = [2, 3, 2]
Output: 3
Explanation: Houses 0 and 2 are adjacent (circle), so you cannot take both 2s. Rob house 1.
Input:  nums = [1, 2, 3, 1]
Output: 4
Explanation: Rob house 0 (1) and house 2 (3).
Input:  nums = [1, 2, 3]
Output: 3

Constraints

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

๐Ÿ’ก Key Insight

The circle only adds one constraint: house 0 and house n - 1 can't both be robbed. So at least one of them is skipped, which means the optimal plan lives entirely inside nums[0..n-2] or inside nums[1..n-1]. Both are ordinary straight-line House Robber problems: answer = max(rob(0, n-2), rob(1, n-1)), each solved with f(i) = max(f(i - 1), nums[i] + f(i - 2)). A single house (n == 1) is the one special case.

Recursion (Brute Force)

TimeO(2โฟ)SpaceO(n)

Intuition

Break the circle by deciding up front which end is excluded: either the last house is off-limits (houses 0..n-2) or the first one is (houses 1..n-1). Inside each range, try every rob/skip combination recursively, moving forward from the range start.

Recurrence (within a range ending at end): f(i) = max(f(i + 1), nums[i] + f(i + 2)), with f(i) = 0 for i > end.

Algorithm

  1. If n == 1 return nums[0].
  2. dfs(i, end): if i > end return 0; else return max(dfs(i + 1, end), nums[i] + dfs(i + 2, end)).
  3. Return max(dfs(0, n - 2), dfs(1, n - 1)).

Visualization

Linear recursion on the range [1, 3] of nums = [1, 2, 3, 1] (index shown). f(3) is solved twice.

flowchart TD
    A["f(1)"] -->|"skip 1"| B["f(2)"]
    A -->|"rob 1: +2"| C["f(3)"]
    B -->|"skip 2"| D["f(3)"]
    B -->|"rob 2: +3"| E["f(4) = 0"]
    C -->|"skip 3"| F["f(4) = 0"]
    C -->|"rob 3: +1"| G["f(5) = 0"]
    D -->|"skip 3"| H["f(4) = 0"]
    D -->|"rob 3: +1"| I["f(5) = 0"]

Range [1, 3] gives max(2 + 1, 3) = 3; range [0, 2] gives 1 + 3 = 4; answer 4.

Code

java
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return nums[0];
        }
        // either skip the last house or skip the first house
        return Math.max(dfs(nums, 0, n - 2), dfs(nums, 1, n - 1));
    }

    // best loot from houses i..end (straight line)
    private int dfs(int[] nums, int i, int end) {
        if (i > end) {
            return 0;
        }
        int skip = dfs(nums, i + 1, end);
        int take = nums[i] + dfs(nums, i + 2, end);
        return Math.max(skip, take);
    }
}

Complexity

  • Time: O(2โฟ) โ€” two branches per house, done twice.
  • Space: O(n) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Recursion (Brute Force) O(2โฟ) O(n)
Top-Down Memoization O(n) O(n)
Bottom-Up Tabulation O(n) O(n)
Space-Optimized DP O(n) O(1)

Common Pitfalls

  • Forgetting n == 1: both ranges become empty and you'd return 0 instead of nums[0].
  • Reusing one memo across both ranges โ€” f(i) on [0, n-2] is not the same as f(i) on [1, n-1].
  • Trying max(rob(nums), ...) on the whole array โ€” the linear answer may take both ends, which is illegal here.
  • Thinking you need a third case "skip both ends" โ€” it's already covered by either range.