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.
Problem Statement
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
Output: 4
Explanation: Rob house 0 (money=1) + house 2 (money=3) = 4.
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.
Output: 3
Constraints
1 <= nums.length <= 1000 <= 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
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.
The Two Scenarios — Visualised
For nums = [1, 2, 3, 1] (n=4), the two subarrays we pass to House Robber I are:
The Algorithm — Step by Step
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.
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.
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.
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]
| Step | val | prev2 | prev1 | curr = max(prev1, prev2+val) |
|---|---|---|---|---|
| init | — | 0 | 0 | — |
| val=2 | 2 | 0 | 0 | max(0, 0+2) = 2 |
| val=7 | 7 | 0 | 2 | max(2, 0+7) = 7 |
| val=9 | 9 | 2 | 7 | max(7, 2+9) = 11 |
| val=3 | 3 | 7 | 11 | max(11, 7+3) = 11 |
| Scenario 1 result = 11 | ||||
Scenario 2 — skip_first = [7, 9, 3, 1]
| Step | val | prev2 | prev1 | curr = max(prev1, prev2+val) |
|---|---|---|---|---|
| init | — | 0 | 0 | — |
| val=7 | 7 | 0 | 0 | max(0, 0+7) = 7 |
| val=9 | 9 | 0 | 7 | max(7, 0+9) = 9 |
| val=3 | 3 | 7 | 9 | max(9, 7+3) = 10 |
| val=1 | 1 | 9 | 10 | max(10, 9+1) = 10 |
| Scenario 2 result = 10 | ||||
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
“Only one house, nothing to be adjacent with. Returnnums[0] = 5. Without the base case,nums[:-1]would be an empty list androbLinearwould return 0 — wrong.”
“Scenario 1:[3]→ 3. Scenario 2:[7]→ 7. Answer: 7. Correct — they’re adjacent in the circle, you can only pick one.”
“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.”
“Each scenario gets a 4-element subarray. House Robber I on 4 equal values picks alternate houses for 2×5=10. Answer: 10. Correct.”
“Both scenarios return 0. Answer: 0. No special casing needed.”
Time and Space Complexity
| Dimension | Complexity | Reason |
|---|---|---|
| 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:
