House Robber
The problem: a street of houses, each holding some amount of money. You can rob any set of them, with one rule — rob two adjacent houses and the alarm goes off. What is the most money you can take?
Climbing stairs asked how many. This one asks which is best — and that single change turns a counting problem into an optimization problem. Six beats, each unlocked by the one before: rob the street by hand, find the one question that splits every haul, follow the recursion, add memory, build the table bottom-up, then carry the idea to a circle and a minimum.
Play the game
Rob the richest set of houses on a 6-house street without ever taking two in a row.
Discover the rule
Every haul answers one yes/no question. That question is the recurrence.
One question, asked once
Every legal haul either robs house 5 or it doesn't. There is no third option. Sort your hauls by that one question and watch what each pile is worth.
houses 0, 2, 5 = 19houses 0, 2, 4 = 12houses 0 = 2houses 0, 2 = 11houses 0, 3 = 5houses 0, 3, 5 = 13Skips house 5
These never touch the last house, so the best of them is exactly the answer for houses 0..4.
Robs house 5
Robbing house 5 rules out house 4, so the rest must come from houses 0..3.
Finish beat 1 to unlock
Follow recursion
Run the rule as function calls and find the work being redone.
Recurrence
The rule says what an answer depends on:
rob(i) = max(rob(i-1), nums[i] + rob(i-2))Recursion
The code follows that rule by calling itself until it reaches rob(0) or rob(1), the two hauls you can read straight off the street.
function rob(i, nums) {
if (i === 0) return nums[0];
if (i === 1) return Math.max(nums[0], nums[1]);
return Math.max(
rob(i - 1, nums),
nums[i] + rob(i - 2, nums));
}Run the recursive code for rob(5). Tap a yellow call to reveal the calls it makes. Keep going until the tree is complete.
1 / 15 callsFinish beat 2 to unlock
Add memory
Decide when the cache already holds the answer.
Same recursion, one new ingredient
Recursion still starts at rob(6) and asks about smaller stretches of the street. This time it carries a cache, so an answer can be saved once and reused.
Current call stack
rob(6)rob(5)rob(4)rob(3)rob(2)Top-down means we began with rob(6) and followed its smaller dependencies.
Cache
The base cases start filled because their answers are already defined.
Decision 1 of 3
Recursion requests rob(2). What should it do?
Finish beat 3 to unlock
Build bottom-up
Fill the same table in order, and confirm the number you predicted.
New task: build the same answers without recursive calls
Memoization is top-down
Start at the far end of the street and recurse back toward house 0, saving answers as they return.
Tabulation is bottom-up
Start at house 0 and fill each answer once its two dependencies exist.
Both store the same meaning: the most loot obtainable from houses 0..i. Only the order changes.
Press start. The animation stops at every cell YOUR recurrence must fill.
Before you fill: two starting facts
Given base casesdp[0] = 2One house available, no adjacency to worry about. Take it.
dp[1] = max(2, 7) = 7Houses 0 and 1 are next door, so you take the richer one and never both.
From dp[2] onward, every cell is the max of two options — and that is the cell the table will stop and ask you for.
- 1
function rob(nums) { - 2
const dp = [nums[0]]; - 3
dp[1] = Math.max(nums[0], nums[1]); - 4
for (let i = 2; i < nums.length; i++) { - 5
dp[i] = Math.max(dp[i-1], nums[i] + dp[i-2]); - 6
} - 7
return dp[nums.length - 1]; - 8
}
Finish beat 4 to unlock
Make it transfer
A circle, a minimum, a looser constraint. Same derivation each time.
Variation 1: the houses are now in a circle, so house 0 and the last house are adjacent too. What's the cleanest fix?
Variation 2: same street, but now you want the MINIMUM loot while still robbing at least one house in every window of three. What changes?
Variation 3: you may skip at most two houses in a row (so no three consecutive skips). Which predecessors does dp[i] need?
Final check: climbing stairs added its two buckets; house robber takes the max of them. Why the difference?
Finish beat 5 to unlock
The pattern, formally
The state: dp[i] is the most money obtainable from houses 0..i. The recurrence: house i is either skipped, leaving dp[i-1], or robbed, which forbids house i-1 and leaves nums[i] + dp[i-2]. So dp[i] = max(dp[i-1], nums[i] + dp[i-2]) with dp[0] = nums[0] and dp[1] = max(nums[0], nums[1]). The dependency shape is identical to climbing stairs; only the combine step differs, because this problem chooses instead of counting.
Naive recursion
Start at the last house, branch on skip vs rob, store nothing.
O(2ⁿ) time
O(n) call stack
Memoization
Same branching, but each index is resolved once and cached.
O(n) time
O(n) cache plus call stack
Tabulation
Walk the street once, one comparison per house.
O(n) time
O(n) table, reducible to O(1)
Takeaways
- One question — does this haul take the last house? — splits every possibility into two groups. That split IS the recurrence.
- Counting problems add their buckets. Optimization problems take the best bucket. The partition is the same move either way.
- Robbing house i forbids house i-1, which is why the second term reaches back to dp[i-2] rather than dp[i-1].
- Greedy fails here: taking the largest house first can cost you two neighbours worth more together. The table considers both futures.
- The dependency graph matches climbing stairs exactly, so recursion wastes work in exactly the same way, and the same two fixes apply.
- Space drops to O(1) once you notice only dp[i-1] and dp[i-2] are ever read.