House Robber II — Breaking Down the Circular Problem (Step-by-Step)

House Robber II — Breaking Down the Circular Problem (Step-by-Step)

House Robber II adds one word to the original problem — circular. That single change seems to make everything harder. But here’s the trick: it actually reduces to two House Robber I problems. Solve each one, take the max, and you’re done.

💡
Before reading this: This problem is heavily based on House Robber I (LeetCode 198). If you haven’t studied that problem yet, start there first — this solution builds directly on top of it with only a small modification.

Problem Statement

LeetCode 213 Medium

You are a professional robber planning to rob houses arranged in a circle. Each house has a certain amount of money stashed. Adjacent houses have a security system — robbing two adjacent houses triggers an alarm.

Since the houses are arranged in a circle, the first and last house are also adjacent.

Given an integer array nums representing the money at each house, return the maximum amount you can rob without alerting the police.

Examples

Example 1
Input: nums = [1, 2, 3, 1]
Output: 4
Explanation: Rob house 0 (money=1) + house 2 (money=3) = 4.
Example 2 — Showing the circular effect
Input: nums = [9, 1, 2, 9]
Output: 11
Explanation: Both 9s are adjacent in the circle — you can’t take both.
Best option: rob house 0 (9) + house 2 (2) = 11.
Example 3
Input: nums = [2, 3, 2]
Output: 3

Constraints

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

Understanding the Test Cases

The first test case [1, 2, 3, 1] — you can rob house 0 (1) and house 2 (3) for a total of 4. House 0 and house 3 are adjacent in the circle, but we didn’t choose both, so no alarm.

The second test case [9, 1, 2, 9] shows the real constraint. In the original House Robber I, you could grab both 9s since they aren’t adjacent in a row. But here, the houses wrap around — house 0 and house 3 are neighbours. So you have to choose: rob house 0 (9) and house 2 (2) for 11, or rob house 1 (1) and house 3 (9) for 10. The answer is 11.

“Brute force and greedy both fail here — just like in House Robber I. The trick is to recognise which part of this problem is new and which part is already solved.”

The Key Insight — The Circular Problem Hides Two Linear Problems

💡 Core Insight

Look at the circular arrangement carefully. There is always one critical constraint: house 0 and house n−1 cannot both be robbed — they are adjacent in the circle.

This means every valid solution falls into one of exactly two cases:

Case 1: You rob house 0, so you must skip house n−1. The problem becomes linear over houses 0 through n−2.

Case 2: You skip house 0, so house n−1 is available. The problem becomes linear over houses 1 through n−1.

Each case is exactly House Robber I on a subarray. Run both, take the maximum — that’s the complete solution.

h[0] $1 h[1] $2 h[2] $3 h[3] $1 ⚠ adjacent Available Robbed (optimal) Circular adjacency

The Two Scenarios — Visualised

For nums = [1, 2, 3, 1] (n=4), the two subarrays we pass to House Robber I are:

Scenario 1 — Skip last house
nums[0 .. n−2] = [1, 2, 3]
1
2
3
1
Houses 0–2 in a row → House Robber I → max = 4 (rob 1+3)
Scenario 2 — Skip first house
nums[1 .. n−1] = [2, 3, 1]
1
2
3
1
Houses 1–3 in a row → House Robber I → max = 3 (rob 2+1)
✓ Final Answer
answer = max(scenario_1, scenario_2) = max(4, 3) = 4

The Algorithm — Step by Step

1 Handle the base case

If the array has only one house, return nums[0] — there’s no circular adjacency to worry about and the two-subarray logic would fail on an empty slice.

2 Build the two subarrays

Create skip_last = nums[0 .. n−2] (skip the last house) and skip_first = nums[1 .. n−1] (skip the first house). These are the only two possible ways to break the circular constraint — every valid solution belongs to one of them.

3 Run House Robber I on each subarray

Use the same logic from House Robber I — two rolling variables prev2 and prev1, updating with curr = max(prev1, prev2 + val) at each step. This gives the optimal loot for each linear subproblem in O(n) time and O(1) space.

4 Return the maximum of both results

max(robLinear(skip_last), robLinear(skip_first)) is the answer. The globally optimal solution is guaranteed to be found in one of the two cases.


Dry Run of the Code — nums = [2, 7, 9, 3, 1]

n = 5. Skip-last subarray: [2, 7, 9, 3]. Skip-first subarray: [7, 9, 3, 1]. Rolling variables start at prev2=0, prev1=0.

Scenario 1 — skip_last = [2, 7, 9, 3]

Stepvalprev2prev1curr = max(prev1, prev2+val)
init00
val=2200max(0, 0+2) = 2
val=7702max(2, 0+7) = 7
val=9927max(7, 2+9) = 11
val=33711max(11, 7+3) = 11
Scenario 1 result = 11

Scenario 2 — skip_first = [7, 9, 3, 1]

Stepvalprev2prev1curr = max(prev1, prev2+val)
init00
val=7700max(0, 0+7) = 7
val=9907max(7, 0+9) = 9
val=3379max(9, 7+3) = 10
val=11910max(10, 9+1) = 10
Scenario 2 result = 10
✓ Final Answer
max(11, 10) = 11  → rob houses at index 1 (7) and index 2 (9) in the skip-last range

The Code

class Solution:
    def rob(self, nums: List[int]) -> int:
        # Base case: only one house, no circular adjacency to handle
        if len(nums) == 1:
            return nums[0]

        def robLinear(houses: List[int]) -> int:
            """House Robber I on a non-circular array — O(n) time, O(1) space."""
            prev2, prev1 = 0, 0
            for val in houses:
                curr  = max(prev1, prev2 + val)
                prev2 = prev1
                prev1 = curr
            return prev1

        # Scenario 1: skip last house → houses 0 to n-2
        # Scenario 2: skip first house → houses 1 to n-1
        return max(robLinear(nums[:-1]), robLinear(nums[1:]))

The robLinear helper is identical to the House Robber I solution — not re-engineered, just reused. That’s the key pattern to highlight in an interview: recognise that you’re building on a solved subproblem, not starting from scratch.


Edge Cases

1 Single house — [5]
“Only one house, nothing to be adjacent with. Return nums[0] = 5. Without the base case, nums[:-1] would be an empty list and robLinear would return 0 — wrong.”
2 Two houses — [3, 7]
“Scenario 1: [3] → 3. Scenario 2: [7] → 7. Answer: 7. Correct — they’re adjacent in the circle, you can only pick one.”
3 The circular constraint matters — [9, 1, 2, 9]
“In a row, you’d take 9+9=18. But they’re circular neighbours. Scenario 1: [9,1,2] → 11. Scenario 2: [1,2,9] → 10. Answer: 11 — correctly blocks the invalid double-9 pick.”
4 All houses same value — [5, 5, 5, 5, 5]
“Each scenario gets a 4-element subarray. House Robber I on 4 equal values picks alternate houses for 2×5=10. Answer: 10. Correct.”
5 All zeros — [0, 0, 0]
“Both scenarios return 0. Answer: 0. No special casing needed.”

Time and Space Complexity

DimensionComplexityReason
Time O(n) Two linear passes over subarrays of length n−1 — total work is O(n)
Space O(n) Python slices create O(n) copies; if index bounds are passed to the helper instead, space is O(1)
“Time is O(n) — the same as House Robber I, just run twice. Space is O(n) because of the list slices. To get true O(1) extra space, pass start and end indices to the helper and iterate over the original array directly.”

Interview Checklist for House Robber II

  • Mention House Robber I first — this solution is built on top of it
  • State the only new constraint: first and last houses are adjacent in a circle
  • Explain that you can’t rob both house 0 and house n−1 → two exhaustive cases
  • Case 1: skip last house → robLinear on nums[0..n−2]
  • Case 2: skip first house → robLinear on nums[1..n−1]
  • Answer = max of both cases
  • Handle n=1 as a base case before running the two-subarray logic
  • Describe robLinear: two rolling variables, curr = max(prev1, prev2 + val)
  • State O(n) time and O(n)/O(1) space depending on implementation
  • Trace through the dry run with [9,1,2,9] to show the circular constraint being enforced

Related Problems to Practice

If you can explain House Robber II cleanly, try these next:

💡 On LeetCode, many problems come in parts — Part I, Part II, Part III. The pattern is almost always the same: Part II adds one constraint to Part I. If your Part I solution is clean and well-structured, Part II usually only needs a small modification. Don’t re-engineer from scratch — build on what you already solved. That’s the lesson House Robber II teaches more than any algorithm.
Scroll to Top