Problem overview
rob receives an array nums where nums[i] is the money in house i. The houses stand in a circle, so the last house is a neighbor of the first. Robbing two neighboring houses on the same night alerts the police. The function returns the largest total you can rob without robbing two neighbors.
For nums = [2, 3, 2] the answer is 3. Houses 0 and 2 hold 2 each, but they are neighbors in the circle, so the best plan is to rob house 1 alone. For [1, 2, 3, 1] the answer is 4: rob houses 0 and 2 for 1 + 3.
The statement sets an O(n) time budget.
Approach: break the circle in two places
On a straight street with two ends, the problem is the classic House Robber. At each house you either skip it and keep the best total so far, or rob it and add its money to the best total from two houses back. That needs only two running numbers.
The circle adds exactly one rule: the first and the last house can't both be robbed. So every valid plan leaves out the first house, or the last one, or both. That gives two straight streets:
- houses
0ton - 2(the last house left out); - houses
1ton - 1(the first house left out).
Each of them has two ends, so the straight-street method solves it. A plan that skips both end houses fits both streets, which does no harm because we only want the maximum. The answer is the larger of the two results.
A lone house is a special case. It has no neighbor, so you rob it, but both streets above would be empty. That's why the solution handles n == 1 first.
The straight-street method visits each house once, and we run it twice, so the total is O(n).
Step by step
- Count the houses. Store
nums.lengthinconst n. - One house. If
n === 1, returnnums[0]. - Leave out the last house.
this.robRange(nums, 0, n - 2)giveswithoutLast. We use the helper before writing it, which keepsrobshort. Inside a class, one method calls another throughthis. - Leave out the first house.
this.robRange(nums, 1, n - 1)giveswithoutFirst. - Take the better one with
Math.max. - The helper.
robRange(nums, start, end)returns the best total for housesstarttoend, treated as a street with two ends. - Two running totals.
prevandcurr, bothletand both starting at 0. At the start of each round,curris the best total for the houses before housei, andprevthe best for the houses before housei - 1. - The loop.
iruns fromstartup to and includingend, so it uses<=. - The choice.
best = Math.max(curr, prev + nums[i]): skip houseiand keepcurr, or rob it and add its money toprev, leaving housei - 1alone. - Shift. Set
prev = currfirst, thencurr = best. The reverse order would overwrite the valueprevneeds. - Return
currafter the loop.
The solution in TypeScript
class Solution {
rob(nums: number[]): number {
const n = nums.length;
if (n === 1) {
return nums[0];
}
const withoutLast = this.robRange(nums, 0, n - 2);
const withoutFirst = this.robRange(nums, 1, n - 1);
return Math.max(withoutLast, withoutFirst);
}
robRange(nums: number[], start: number, end: number): number {
let prev = 0;
let curr = 0;
for (let i = start; i <= end; i++) {
const best = Math.max(curr, prev + nums[i]);
prev = curr;
curr = best;
}
return curr;
}
}
TypeScript details
let prev = 0;needs no annotation: TypeScript infersnumberfrom the initial value.constis used for values that never change, andletfor the two totals that do.numberis a double, so there is no integer overflow. With at most 100 houses of at most 1000 each, totals stay far below the point where doubles lose integer precision.===compares without type conversion and is the usual choice in TypeScript.- When the range is empty (
end < start), the loop body never runs androbRangereturns 0. For examplerobRange([5], 0, -1)returns 0.
Tracing an example
For nums = [1, 2, 3, 1], the first street is houses 0 to 2. Running the loop prints these values:
i | nums[i] | prev | curr | best |
|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 1 |
| 1 | 2 | 0 | 1 | 2 |
| 2 | 3 | 1 | 2 | 4 |
So withoutLast is 4. The second street, houses 1 to 3, gives best values 2, 3 and 3, so withoutFirst is 3. The larger one, 4, is the answer.
Complexity
Time: O(n). Each call of robRange loops over at most n - 1 houses with constant work per house, and rob makes two calls.
Space: O(1). Only a few variables are used, whatever the number of houses.
Edge cases and common mistakes
- A single house.
[5]returns 5. Without the early return, both calls would get an empty range and the answer would be 0. - Two houses. They are neighbors in both directions, so you can rob only one. For
[1, 2], the first street is just house 0 (1), the second is house 1 (2), and the result is 2. - Solving it as a plain street. For
[400, 0, 0, 400, 0, 0, 400], a street with two ends gives 1200, but the circle only allows 800. - Using
<instead of<=in the loop. The last house of the range would be skipped. - Updating the totals in the wrong order. Assign
curr = bestbeforeprev = currand both end up equal tobest. - Zeros. Houses may hold 0.
[0, 0, 0]returns 0, which is correct.
Testing
Running rob on a few arrays gives:
nums | Result |
|---|---|
[2,3,2] | 3 |
[1,2,3,1] | 4 |
[400,0,0,400,0,0,400] | 800 |
[5] | 5 |
[1,2] | 2 |
[2,1,1,2] | 3 |
[0,0,0] | 0 |
