Loading problems...
TL;DR: The optimal solution breaks the circular constraint by decomposing the problem into two linear "House Robber" subproblems—one excluding the first house and one excluding the last—and returning the maximum result of the two.
House Robber II asks us to determine the maximum amount of money one can steal from a series of houses arranged in a circle. The critical constraint is that adjacent houses cannot be robbed on the same night. Because the arrangement is circular, the first house and the last house are considered neighbors. This implies you cannot rob both the first and the last house simultaneously.
This problem is a direct extension of the classic linear dynamic programming problem found in LeetCode 198. The LeetCode 213 solution requires handling the "wrap-around" adjacency effectively.
The brute force approach attempts to explore every valid combination of houses. We can define a recursive function that makes a decision at every house: either rob it (and skip the next one) or skip it (and consider the next one).
To handle the circular constraint in a brute force manner, one might simply run the recursion while maintaining a flag indicating if the first house was robbed. If the recursion reaches the last house and the first house was also robbed, that path is invalid.
Pseudo-code:
text
function rob(index, robbedFirst):
if index > last_index:
return 0
if index == last_index and robbedFirst:
return 0 // Cannot rob last if first was robbed
// Option 1: Rob current house
val1 = nums[index] + rob(index + 2, index == 0 ? true : robbedFirst)
// Option 2: Skip current house
val2 = rob(index + 1, robbedFirst)
return max(val1, val2)Time Complexity: . At every step, the recursion branches into two possibilities. This exponential growth makes the solution infeasible for up to 100. Why it fails: The approach performs redundant calculations for overlapping subproblems (e.g., the optimal way to rob houses 5 through 10 is calculated multiple times). This leads to a Time Limit Exceeded (TLE) error.
The primary challenge in House Robber II is the circular dependency: House and House are neighbors. This breaks the simple linear recurrence relation used in House Robber I.
However, we can simplify the circle into linear segments based on a key observation: You cannot rob both House and House .
This constraint leaves us with exactly two scenarios covering all valid possibilities:
nums[0...N-2].nums[1...N-1].By solving these two linear subproblems, we effectively break the circle. The answer to the original circular problem is simply max(Scenario A, Scenario B).
Each scenario is now a standard linear "House Robber" problem, which is solved using the Fibonacci-style recurrence:
Visual Description:
Imagine the state transition as a sliding window of two variables, prev1 and prev2. As we iterate through the array, prev1 represents the maximum loot up to the previous house, and prev2 represents the maximum loot up to the house before that. At current house , we calculate the new maximum and shift the window forward. We perform this linear scan twice: once for the array excluding the tail, and once for the array excluding the head.

Consider input nums = [2, 3, 2]. Length is 3.
0 to 1 (Values [2, 3]).1 to 2 (Values [3, 2]).rob1 = 0, rob2 = 0.new = max(0 + 2, 0) = 2. rob1=0, rob2=2.new = max(0 + 3, 2) = 3. rob1=2, rob2=3.rob1 = 0, rob2 = 0.new = max(0 + 3, 0) = 3. rob1=0, rob2=3.new = max(0 + 2, 3) = 3. rob1=3, rob2=3.max(3, 3) = 3.The correctness relies on the fact that the circular constraint only adds one restriction: indices and cannot be chosen together. The set of all valid robbing plans can be partitioned into two subsets:
0 to N-2.1 to N-1.Any valid plan must belong to at least one of these subsets. By taking the maximum of the optimal solutions for both subsets, we guarantee finding the global maximum for the circular arrangement. The internal logic of the linear solver is proven correct via induction on the recurrence relation .
cppclass Solution { public: int rob(vector<int>& nums) { int n = nums.size(); if (n == 0) return 0; if (n == 1) return nums[0]; // Solve two linear subproblems // 1. Include first house, exclude last (0 to n-2) // 2. Exclude first house, include last (1 to n-1) return max(robLinear(nums, 0, n - 2), robLinear(nums, 1, n - 1)); } private: // Helper function for linear House Robber logic int robLinear(const vector<int>& nums, int start, int end) { int prev2 = 0; // dp[i-2] int prev1 = 0; // dp[i-1] for (int i = start; i <= end; ++i) { int current = max(prev1, prev2 + nums[i]); prev2 = prev1; prev1 = current; } return prev1; } };
javaclass Solution { public int rob(int[] nums) { if (nums == null || nums.length == 0) return 0; if (nums.length == 1) return nums[0]; // Calculate max loot for the two scenarios int max1 = robLinear(nums, 0, nums.length - 2); int max2 = robLinear(nums, 1, nums.length - 1); return Math.max(max1, max2); } private int robLinear(int[] nums, int start, int end) { int prev2 = 0; // Represents dp[i-2] int prev1 = 0; // Represents dp[i-1] for (int i = start; i <= end; i++) { int current = Math.max(prev1, prev2 + nums[i]); prev2 = prev1; prev1 = current; } return prev1; } }
pythonclass Solution: def rob(self, nums: List[int]) -> int: n = len(nums) if n == 0: return 0 if n == 1: return nums[0] # Define helper to solve linear version def rob_linear(start, end): rob1, rob2 = 0, 0 # Iterate through the specified range for i in range(start, end + 1): # Standard DP transition: max(skip, rob + prev_rob) new_rob = max(rob2, rob1 + nums[i]) rob1 = rob2 rob2 = new_rob return rob2 # Compare scenario 0 to n-2 vs scenario 1 to n-1 return max(rob_linear(0, n - 2), rob_linear(1, n - 1))
rob1, rob2) to track the state of the previous two houses within the helper function.The DP - 1D Array (Fibonacci Style) pattern and its logic are directly applicable to several other popular interview questions:
Why does my solution fail for single-element arrays?
nums.length == 1 explicitly.0 to n-2 and 1 to n-1, both ranges are empty or invalid when .nums[0], causing a Wrong Answer.Why do I get Time Limit Exceeded (TLE)?
Why is my space complexity O(N)?
dp = [0] * n) inside the helper function.Why does my logic fail on small circles (e.g., size 2 or 3)?
start and end indices for the two scenarios.