An integer array nums is given, where each value represents money kept in one house. Houses are arranged in a circle, so the first house and the last house are adjacent.
No two adjacent houses can be robbed during the same night. Return the maximum amount of money obtainable.
Example 1
Input: nums = [2, 3, 2]
Output: 3
Explanation: The first and last houses are adjacent, so both houses with value 2 cannot be robbed together. Robbing the middle house gives the maximum amount.
Example 2
Input: nums = [1]
Output: 1
Explanation: A single house has no adjacent conflict, so the only available amount is returned.
Recursion
Start with the small, comforting part of the problem. In a straight row of houses, each house gives two choices: take the current house and move two positions back, or skip the current house and move one position back.
The circle only changes the endpoints. Robbing the first house blocks the last house, and robbing the last house blocks the first house. So the circular problem can be split into two straight-row problems: houses from 0 to n - 2, and houses from 1 to n - 1.
The recursive state solve(index, start) stores the best amount from start through index. The first helper call starts from the ending index of each valid range because the decision is made from right to left until the starting boundary is crossed.
Algorithm
Handle the single-house case first because one house has no circular neighbor conflict.
Split the circle into ranges
0throughn - 2and1throughn - 1so no range contains both circular endpoints.Define
solve(index, start)as the best amount fromstartthroughindexso every recursive call represents a smaller prefix of one valid range.Stop beyond
startwith0, and stop atstartwithnums[start], because both boundaries have only one possible answer.Compute the skip amount from
index - 1because leaving the current house untouched keeps the previous prefix valid.Compute the take amount from
nums[index]plussolve(index - 2, start)because taking a house makes the adjacent previous house unavailable.Return the larger choice for every state, then compare both range answers so the best valid circular total becomes the final answer.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the best amount from start through index. int solve(int index, int start, vector<int>& nums) { // No house remains inside the active range. if (index < start) { return 0; } // Only the starting house remains inside the active range. if (index == start) { return nums[start]; } // Skipping keeps the previous house available. int skip = solve(index - 1, start, nums); // Taking earns the current amount. // A two-step jump avoids the adjacent house. int take = nums[index] + solve(index - 2, start, nums); // The better valid choice is kept for the current range. return max(skip, take); }public: // Returns the maximum money obtainable from circular houses. int rob(vector<int>& nums) { int n = nums.size(); // A single house has no circular adjacency conflict. if (n == 1) { return nums[0]; } int excludeLast = solve(n - 2, 0, nums); int excludeFirst = solve(n - 1, 1, nums); return max(excludeLast, excludeFirst); }};// Driver codeint main() { vector<int> nums = {2, 3, 2}; Solution obj; cout << obj.rob(nums); return 0;}Complexity Analysis
Time Complexity: O(2N), where N is the number of houses, because each non-base recursive state can branch into two choices: take or skip.
Space Complexity: O(N), because the recursion stack can grow to a depth of at most N while processing one linear range.
Memoization
Direct recursion repeats the same range answers. For example, a state such as solve(1, 0) can be reached from multiple branches while the answer remains unchanged.
Memoization adds a dp array for the active linear range. After a state is solved once, the saved value is reused immediately. The circular split and the take-or-skip decision stay exactly the same; only repeated recursion disappears.
Algorithm
Handle the single-house case first because one available amount is already the maximum valid total.
Process the two allowed ranges separately so the first and last houses never enter the same recursive search.
Fill a
dparray with-1for each range so every untouched index clearly marks an unsolved state.Keep
solve(index, start)unchanged from recursion so cached states preserve the same prefix meaning and take-or-skip choices.Return
0beyondstartandnums[start]atstartbecause both boundary states have direct answers.Reuse
dp[index]after a cache hit because the same range state always produces the same maximum amount.Store the larger take-or-skip amount in
dp[index], then compare both range answers so every state is solved once per range.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the cached best amount from start through index. int solve(int index, int start, vector<int>& nums, vector<int>& dp) { // No house remains inside the active range. if (index < start) { return 0; } // Only the starting house remains inside the active range. if (index == start) { return nums[start]; } // A completed state is reused to avoid repeated recursion. if (dp[index] != -1) { return dp[index]; } // Skipping keeps the previous house available. int skip = solve(index - 1, start, nums, dp); // Taking earns the current amount. // A two-step jump avoids the adjacent house. int take = nums[index] + solve(index - 2, start, nums, dp); // The better valid choice is saved for future calls. dp[index] = max(skip, take); return dp[index]; } // Solves one range after the circle is split. // Chosen endpoints prevent a circular clash. int robLinear(int start, int end, vector<int>& nums) { vector<int> dp(nums.size(), -1); return solve(end, start, nums, dp); }public: // Returns the maximum money obtainable from circular houses. int rob(vector<int>& nums) { int n = nums.size(); // A single house has no circular adjacency conflict. if (n == 1) { return nums[0]; } int excludeLast = robLinear(0, n - 2, nums); int excludeFirst = robLinear(1, n - 1, nums); return max(excludeLast, excludeFirst); }};// Driver codeint main() { vector<int> nums = {2, 3, 2}; Solution obj; cout << obj.rob(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of houses, because each of the two linear ranges contains at most N - 1 states and every state is computed once.
Space Complexity: O(N), because the dp array and recursion stack each require linear space for one range.
Tabulation
Memoization solves a range from the end backward through recursive calls. Tabulation builds the same answers from the start forward.
For one straight range, dp[index] stores the best amount from the range start through index. Processing house index uses dp[index - 1] for the skip amount and nums[index] + dp[index - 2] for the take amount.
The circular part remains unchanged. Solve both straight ranges in table form, then return the larger result.
Algorithm
Return the single-house amount directly because no neighboring house can create a circular conflict.
Evaluate ranges
0throughn - 2and1throughn - 1so each table represents a valid straight row.Create a
dparray for each range sodp[index]can preserve the best amount through every processed house.Set
dp[start] = nums[start]because the first allowed house forms the only non-empty choice at the range boundary.Move from
start + 1throughendin increasing order so both earlier states are ready before every transition.Read the skip amount from
dp[index - 1]and build the take amount fromnums[index]plus the available two-step state because adjacent houses cannot be combined.Store the larger choice in
dp[index], then compare both range totals so the circular answer keeps the best legal table result.
Dry Run
House Robber II tabulation
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the best amount from one straight range. int robLinear(int start, int end, vector<int>& nums) { vector<int> dp(nums.size(), 0); dp[start] = nums[start]; // Builds each state from already completed previous states. for (int index = start + 1; index <= end; index++) { int skip = dp[index - 1]; int previous = 0; // Check for a two-step state. // The range boundary prevents invalid access. if (index - 2 >= start) { previous = dp[index - 2]; } // Taking adds the current amount. // The saved prefix ends before the neighbor. int take = nums[index] + previous; dp[index] = max(skip, take); } return dp[end]; }public: // Returns the maximum money obtainable from circular houses. int rob(vector<int>& nums) { int n = nums.size(); // A single house has no circular adjacency conflict. if (n == 1) { return nums[0]; } int excludeLast = robLinear(0, n - 2, nums); int excludeFirst = robLinear(1, n - 1, nums); return max(excludeLast, excludeFirst); }};// Driver codeint main() { vector<int> nums = {2, 3, 2}; Solution obj; cout << obj.rob(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of houses, because two linear ranges are processed and each contains at most N - 1 houses.
Space Complexity: O(N), because the dp array stores one value for each house in a range, while the iterative approach uses no recursion stack.
Space Optimization
Tabulation stores the complete dp array, but the current answer needs only two older states. dp[index - 1] gives the skip amount, and dp[index - 2] helps calculate the take amount.
For one straight range, previousOne represents the answer through the previous house, and previousTwo represents the answer through the house before the previous house. The current value is calculated from the same take-or-skip choice, then both variables shift forward.
The circular split stays unchanged. Each of the two ranges is solved with rolling variables, and the larger amount is returned.
Algorithm
Return the single-house amount immediately because the only house forms the complete valid answer.
Evaluate the two allowed ranges separately so rolling states never combine both circular endpoints.
Start each range with
previousTwo = 0andpreviousOne = nums[start]because both values match the first two DP dependencies.Move from
start + 1throughendin increasing order so the rolling variables always describe the two earlier prefix answers.Use
previousOneas the skip amount andnums[index] + previousTwoas the take amount because taking the current house must bypass the adjacent house.Calculate
currentas the larger choice so the best amount through the current house remains available.Shift
previousTwobeforepreviousOne, then returnpreviousOne, because the next house needs both old prefix positions in correct order.
Dry Run
House Robber II Space Optimization
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the best amount from one straight range. int robLinear(int start, int end, vector<int>& nums) { int previousTwo = 0; int previousOne = nums[start]; // Advances the rolling states across the active range. for (int index = start + 1; index <= end; index++) { int skip = previousOne; int take = nums[index] + previousTwo; // Calculates current from the better take-or-skip choice. int current = max(skip, take); // Shifts both saved states forward for the next house. previousTwo = previousOne; previousOne = current; } return previousOne; }public: // Returns the maximum money obtainable from circular houses. int rob(vector<int>& nums) { int n = nums.size(); // A single house has no circular adjacency conflict. if (n == 1) { return nums[0]; } int excludeLast = robLinear(0, n - 2, nums); int excludeFirst = robLinear(1, n - 1, nums); return max(excludeLast, excludeFirst); }};// Driver codeint main() { vector<int> nums = {2, 3, 2}; Solution obj; cout << obj.rob(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of houses, because the two linear ranges are processed once and each house is handled a constant number of times.
Space Complexity: O(1), because only a fixed number of variables are used while processing each linear range.
Interview follow-up Questions
The first and last houses are adjacent. Any valid answer must exclude at least one endpoint, so comparing the range without the last house and the range without the first house covers every legal choice.
Be the first to add a comment.