Home DSA House Robber Dynamic Programming: 5 Bold Wins You Must Know
Intermediate 3 min · September 07, 2026

House Robber Dynamic Programming: 5 Bold Wins You Must Know

House Robber DP in O(n) time, O(1) space.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Production
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
Before you start⏱ 20 min
  • Climbing Stairs recurrence thinking
  • Max-based (not sum-based) DP decisions
  • Big-O time and space analysis
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • House Robber asks for the max non-adjacent sum (e.g. [2,7,9,3,1] gives 12 via houses 2 + 9 + 1)
  • Optimal approach: rob-or-skip fold with two rolling variables — O(n) time, O(1) space
  • Key trick: best(i) = max(nums[i] + best(i-2), best(i-1)) — the i-2 jump enforces adjacency structurally
  • Asked at Amazon, Meta, and Google in most DP rounds — the standard max-based DP gateway
✦ Definition~90s read
What is House Robber Dynamic Programming?

House Robber is LeetCode 198, a Medium DP problem and the canonical max-based (rather than counting-based) interview question. Given up to 100 houses with cash values, you rob a non-adjacent subset for maximum total. It appears at Amazon, Meta, Google, and Microsoft because it tests constraint-encoding recurrences, greedy skepticism, and space compression in six lines.

Imagine houses in a row, each with cash inside, but robbing two neighbors triggers an alarm.

The solution folds rob-or-skip over prefixes: best(i) = max(nums[i] + best(i-2), best(i-1)) with zero seeds — O(n) time, O(1) space via rolling variables. The i-2 jump encodes adjacency structurally. Circular streets (House Robber II) run the fold twice with opposite exclusions; tree streets (House Robber III) lift rob/skip to two-state post-order DP; Delete and Earn reframes values as a robber street.

One skeleton, four problems.

Plain-English First

Imagine houses in a row, each with cash inside, but robbing two neighbors triggers an alarm. Walk down the street with two numbers memorized: the best haul so far, and the best haul up to the house before last. At each new house, choose: rob it (its cash plus the before-last best) or skip it (keep the so-far best). Take whichever is bigger, shift your two numbers forward, and continue. At the end of the street, the so-far number is the perfect heist. Two numbers, one pass, no revisits.

House Robber is Climbing Stairs with attitude. Same DP skeleton, one adversarial twist.

Greedy fails here. Grabbing the richest house first loses on [5,1,1,5] — the two 5s aren't adjacent, but greedy adjacency repair still fumbles variants like [2,1,1,2]... you'll code it, feel clever, and fail hidden tests.

The fix is rob-or-skip per house: best(i) = max(nums[i] + best(i-2), best(i-1)). That's O(n) time, O(1) space with rolling variables. You'll derive it in a minute once you think in prefix optima. Let's build that reflex.

Maximise the Take Without Robbing Two Adjacent Houses

Given nums where nums[i] is cash in house i, rob a subset with no two adjacent for maximum total. Example [2,7,9,3,1] → 12 (houses 2 + 9 + 1: indices 0, 2, 4). Check: 2+9+1 = 12 beats 7+3 = 10 and 2+3+1 = 6.

Constraints: n to 100, values to 400. Small limits, but the pattern matters more than the size — circular (House Robber II) and tree (House Robber III) variants reuse this skeleton. Return a total, not the indices (track parents for indices — a standard extension).

Walk [2,7,9,3,1] by prefix optima: best(-1)=best(-2)=0. i=0: max(2+0, 0)=2. i=1: max(7+0, 2)=7. i=2: max(9+2, 7)=11. i=3: max(3+7, 11)=11. i=4: max(1+11, 11)=12. Answer 12. Each decision uses only solved prefixes.

📊 Production Insight
Compute the prefix-best column (2,7,11,11,12) by hand first. Candidates who table prefixes never write greedy.
🎯 Key Takeaway
Max non-adjacent sum; [2,7,9,3,1] → 12 — trace prefix optima before coding.

Why Greedy Picks Wrong and Recursion Repeats the Work

Brute force: enumerate all 2^n subsets, keep valid max. O(2^n) time — dead past n = 25 (33M subsets). Correct, useless.

Greedy richest-first: pick the max house, delete its neighbors, repeat. It stumbles on streets like [1,100,1,1,100,1] where skipping a rich house unlocks better combinations, and repair-order variants fail unpredictably. No exchange argument exists — local riches don't compose under adjacency.

Interview play: 'Subsets are O(2^n) — dead. Greedy has no exchange proof under adjacency — I'll use prefix DP.' Two sentences that show you test strategies, not just remember them.

📊 Production Insight
Ask yourself 'what's the exchange argument?' for any greedy impulse. No argument in 10 seconds means DP — that reflex is seniority.
🎯 Key Takeaway
Subsets cost O(2^n); greedy lacks any exchange proof here — rule both out in seconds.

Rob or Skip: Every House Is a Two-Way Decision

Define best(i) = max haul from houses 0..i. Decide house i: rob it → nums[i] + best(i-2) (neighbor i-1 forbidden); skip it → best(i-1). Take the max: best(i) = max(nums[i] + best(i-2), best(i-1)), with best(-1) = best(-2) = 0.

Proof sketch by induction: bases hold (no houses → 0). Assume prefixes below i optimal. Any optimal i-solution either contains i (then its remainder is an optimal (i-2)-solution by cut-and-paste) or excludes i (then it's an optimal (i-1)-solution). The max of both cases is optimal. Each i computed once forward: O(n) time. Only i-1, i-2 are read → two rolling variables: O(1) space.

This is Climbing Stairs' skeleton with max replacing sum — same shape, adversarial operator. State that parallel live; interviewers reward family recognition.

📊 Production Insight
Say 'cut-and-paste' explicitly: optimal remainder of an optimal solution is optimal. That phrase is the DP-correctness password.
🎯 Key Takeaway
Cut-and-paste optimality on the rob/skip cases proves the recurrence; rolling pair compresses to O(1).

The Rob-or-Skip Solution in Full Python

The code above is the complete LeetCode submission. Trace [2,7,9,3,1]: (0,0) → n=2: cur=2, (0,2) → n=7: cur=7, (2,7) → n=9: cur=11, (7,11) → n=3: cur=10→11, (11,11) → n=1: cur=12, (11,12). Returns 12. Trace []: loop skipped, returns 0. Trace [5]: cur=5, returns 5.

Complexity: O(n) time, O(1) space. No imports, no arrays, no recursion — safe at every limit.

solution.pyPYTHON
1
2
3
4
5
6
7
8
9
10
class Solution:
    def rob(self, nums: list[int]) -> int:
        prev2 = 0  # best up to i-2
        prev1 = 0  # best up to i-1
        for n in nums:
            cur = prev2 + n
            if prev1 > cur:
                cur = prev1
            prev2, prev1 = prev1, cur
        return prev1
⚠ The Jump That Enforces Everything
best(i) = max(nums[i] + best(i-2), best(i-1)) — the i-2 jump IS the adjacency rule. Slip to i-1 and neighbors get robbed.
📊 Production Insight
Run [] → 0 and [5] → 5 live the moment code is written. Zero-seed bugs die in ten seconds against those two.
🎯 Key Takeaway
Fold rob/skip over zero seeds — verify on [], [5], and the 5-house example.

One House, Two Houses and All-Zero Streets

Empty returns 0 (loop skipped). Single [5] returns 5. Two houses [2,1] returns 2 (rob richer). All-zeros returns 0. Alternating [5,1,1,5] returns 10 (both ends, non-adjacent). Large values need no special handling in Python (mention 32-bit int fit for Java/C++).

Shift-order trap: prev2, prev1 = prev1, cur must bind simultaneously (tuple assignment evaluates right-first). Sequential shifts (prev2 = prev1; prev1 = prev2 + n) reuse the new prev2 — trace [1,2,3,1] → must be 4, drifts to 5+ when shifted wrong.

📊 Production Insight
The [1,2,3,1] → 4 probe isolates shift order: wrong shifts inflate it. Keep it in your back pocket for every rolling-DP problem.
🎯 Key Takeaway
Test [], [5], [2,1], all-zeros, alternating — five inputs cover seeds, shifts, and structure.

Why Two Rolling Variables Replace the DP Array

Time O(n): one fold over houses, O(1) per step. Space O(1): two integers. Both meet lower bounds — every house must be considered (Ω(n) floor); only two prefix optima need memory (O(1) floor).

Whiteboard closer: 'Rob-or-skip per prefix, i-2 jump enforces adjacency, rolling pair compresses space.' Then bridge: 'Circle streets run this twice (exclude each end); tree streets lift rob/skip to post-order states.' Family mapped, round won.

📊 Production Insight
Close by naming Robber II (circle) and III (tree). Examiners promote candidates who place the problem in its variant family.
🎯 Key Takeaway
One decision per house meets the read floor; two stored optima meet the space floor.
● Production incidentPOST-MORTEMseverity: high

The 24-Minute Greedy Heist That Fumbled the Alternating Street

Symptom
Greedy passed [2,7,9,3,1] → 12 by luck but returned 6 on [5,1,1,5] (expect 10). The rewrite then crashed on [] with 15 minutes left.
Assumption
The candidate assumed richest-first greedy was 'obviously optimal for maximizing sums' and coded max-picking with adjacency repair. They believed DP was overkill for 'just pick big houses'.
Root cause
Greedy max-picking has no optimality property under adjacency constraints — [5,1,1,5] repairs fumbled to 6 vs true 10. The first DP rewrite seeded with nums[0], crashing on [] and zeroing [5].
Fix
With 12 minutes left the interviewer posed [5,1,1,5] and asked for greedy's answer vs optimal. The candidate saw the gap, installed the rob/skip fold with zero seeds, and passed with 4 minutes left — graded weak hire for needing the counterexample.
Key lesson
  • Greedy needs proof, not instinct — one counterexample ([5,1,1,5]) kills richest-first thinking in seconds.
  • Zero-seed the rolling pair and trace [2,7,9,3,1] → 12 immediately; seeds and shifts are where DP code dies.
Production debug guideFour defect shapes and the exact check that exposes each one.4 entries
Symptom · 01
Adjacent houses both robbed in the total
Fix
Trace [2,7,9,3,1] logging (prev2, prev1) per step: expect haul 12. If robbing any house ever combines with best(i-1) instead of best(i-2), the jump slipped — restore the i-2 term and retrace.
Symptom · 02
Crash on [] or wrong answer on short inputs
Fix
Replace seeds with rob1 = rob2 = 0 and use the uniform fold for all inputs. Rerun [], [5], [2,1] expecting 0, 5, 2. Zero-seeds remove every length special-case.
Symptom · 03
Greedy passes examples but fails alternating patterns
Fix
Test [5,1,1,5] (expect 10) and [2,1,1,2] (expect 4). If greedy-flavored code fails either, delete the max-picking and install the rob/skip fold — local riches mislead, prefix optima don't.
Symptom · 04
Small inputs right, larger inputs inflated (shift-order bug)
Fix
Confirm the update evaluates max() from pre-shift values: tmp = max(prev2 + n, prev1), then shift. Trace [1,2,3,1] → 4 by hand; reversed shifts inflate to 5+.
House Robber Approaches Compared
ApproachTimeSpaceVerdict
Brute force (all subsets)O(2^n)O(n)Dead past n = 25
Memoized recursion (rob/skip)O(n)O(n)Good, clearest derivation
Tabulation array dp[i]O(n)O(n)Good, no recursion limit
Rolling two variablesO(n)O(1)Best: optimal, six lines

Key takeaways

1
House Robber maximizes non-adjacent sums via best(i) = max(nums[i] + best(i-2), best(i-1)).
2
The i-2 jump is the entire adjacency constraint
enforced structurally, not by bookkeeping.
3
Zero-seeds (best(-1) = best(-2) = 0) handle empty and single-house inputs uniformly.
4
Rolling two variables compress O(n) table space to O(1)
always volunteer this.
5
The rob/skip skeleton extends to circular streets (two linear runs) and tree streets (two-state post-order).

Common mistakes to avoid

4 patterns
×

Greedy-picking the richest houses first

Symptom
Crafted cases fail: [1,100,1,1,100,1] style streets where skipping a rich house enables two medium ones. Local riches mislead; prefix optima don't.
Fix
Decide per house from solved prefix states: rob it (nums[i] + prev2) or skip it (prev1), take the max. The constraint is enforced structurally — no adjacency bookkeeping needed.
×

Seeding the rolling pair with nums[0] variants that break on short inputs

Symptom
[] crashes, [5] returns 0, [2,1] returns 1 instead of 2. Zero-seeds plus the fold handle every length uniformly.
Fix
Seed rob1 = 0 (best with no houses), rob2 = 0, then fold each house: tmp = max(rob1 + n, rob2)... precisely new = max(prev_no_adj + nums[i], prev_best). Trace [2,7,9,3,1] → 12 to lock the order.
×

Allowing adjacent picks via index slips (i-1 instead of i-2)

Symptom
[1,2,3,1] returns 7 (1+2+3+1... sums everything) instead of 4. The i-2 jump is the entire constraint — slip it and adjacency dies.
Fix
Write the recurrence as best(i) = max(nums[i] + best(i-2), best(i-1)) with best(-1) = best(-2) = 0. Neighbors are excluded by construction: robbing i jumps to i-2.
×

Allocating a full dp array and stopping there

Symptom
Passes, but 'can you cut the memory?' draws silence. Two rolling variables answer it before it's asked.
Fix
Keep the O(1) rolling pair as the submission. Mention the O(n) table only as the derivation step. The compression question is asked in most House Robber rounds.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Derive the recurrence and the rolling optimization.
Q02SENIOR
How does the solution change for a circular street?
Q03SENIOR
Generalize to House Robber III (binary tree streets)?
Q01 of 03SENIOR

Derive the recurrence and the rolling optimization.

ANSWER
At each house decide rob (value + best up to i-2) or skip (best up to i-1); take the max. best(-1) = best(-2) = 0 seeds it. Each house is decided once going forward: O(n) time. Only two predecessors are read, so two variables give O(1) space.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How does the recurrence prevent adjacent picks?
02
Why is O(1) space possible?
03
Does this extend to robbing houses in a tree?
04
What if the street is a circle?
05
Can anyone beat O(n) time or O(1) space?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Verified
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
🔥

That's Dynamic Programming. Mark it forged?

3 min read · try the examples if you haven't

Previous
Top K Frequent Elements
17 / 17 · Dynamic Programming
Next
Jump Game Greedy Algorithm