HARD Lab · Dynamic Programming · 33

198. House Robber

You can't rob two adjacent houses — so the choice at each house is: skip it and keep the previous best, or rob it and add to what you had two houses back. Two rolling variables replace the full DP array.

MediumDPTypeScript

PROBLEM What we're solving

Given an array of non-negative integers nums where each element is the money in a house, return the maximum amount you can rob tonight without alerting the police — adjacent houses are connected and you cannot rob two houses in a row.

Example: nums = [2, 7, 9, 3, 1]. Rob houses 0, 2, 4 → 2 + 9 + 1 = 12. Any other non-adjacent selection yields less. Answer: 12.

KEY IDEA The i−2 lookback

Insight → at house i, you have exactly two choices: skip it (answer stays dp[i-1]) or rob it (answer becomes dp[i-2] + nums[i]). The adjacency constraint is the entire reason you look back two steps instead of one — robbing house imeans you couldn't have touched house i-1.

RECURRENCE DP formula & rolling variables

  • 0 · Base cases. dp[0] = nums[0] (only one house); dp[1] = max(nums[0], nums[1]) (pick the richer of the first two). In the rolling approach both start at 0 and the loop handles them naturally.
  • 1 · Recurrence. dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Why: either you skipped house i (best is whatever you had through i-1) or you robbed it (last safe house was i-2).
  • 2 · Roll variables. You only ever need the last two values, so keep rob2 (two back) and rob1 (one back). After computing best, shift: rob2 = rob1, rob1 = best.
  • 3 · Answer. rob1 after the loop holds the global maximum.
Classic confusion → the rolling shift order matters. You must save the old rob1 as rob2 before overwriting it. Many learners write rob1 = best; rob2 = rob1 — but by then rob1 is already the new value, so rob2 gets best instead of the old rob1. Always assign rob2 first.

COST Complexity & space reduction

Full DP array
O(n) space
Keeps every dp[i] — only the last two are needed.
Rolling two variables
O(1) space
O(n) time; two scalars replace the array.

Both approaches are O(n) time. The rolling trick drops space from O(n) to O(1) because the recurrence only depends on i-1 and i-2.

Pattern transfer → the same O(1)-space rolling trick appears in House Robber II (circular array — run the linear problem twice, once excluding the first house and once excluding the last), Climbing Stairs (identical recurrence, different story), and Min Cost Climbing Stairs. Any 1-D DP whose recurrence only touches the last one or two rows can be rolled down to O(1) space.

RUN IT Can't rob adjacent — look back two steps

step 0 / 11
START5 housesin a row. Adjacent houses have alarms — you can't rob two in a row. Goal: maximize total loot.
nums2071923314
State
rob2 (dp[i-2])
rob1 (dp[i-1])
nums[i]
best
current houserobbedrob2 (two back)rob1 (one back)
slowfast

TYPESCRIPT The solution, annotated

houseRobber.ts
function rob(nums: number[]): number {
  let rob2 = 0; // best loot up to two houses back (dp[i-2])
  let rob1 = 0; // best loot up to one house back  (dp[i-1])

  for (const n of nums) {
    const best = Math.max(rob1, rob2 + n); // skip vs rob
    rob2 = rob1;
    rob1 = best;
  }

  return rob1;
}

Reading it block by block

Lines 2–3 — rolling state. rob2 represents the best loot through the house two positions back (dp[i-2]) and rob1 represents the best through one position back (dp[i-1]). Both start at 0 so the first iteration works without a special base-case branch.
Line 6 — the recurrence. Math.max(rob1, rob2 + n) encodes the two choices: skip this house (answer stays rob1) or rob it (add n to rob2, the last safe state). The adjacency constraint forces us to reach back to rob2— if we robbed the previous house we can't use it.
Lines 7–8 — advance the window. Shift: rob2 = rob1 then rob1 = best. Order is critical — rob2 must be updated before rob1, otherwise rob2 gets the new best instead of the old one-step-back value.
Line 11 — return. After the loop, rob1 holds the maximum loot across the whole array. For [2, 7, 9, 3, 1] the answer is 12.
Complexity → O(n) time (one linear pass), O(1) space. The full DP array would be O(n) space but is unnecessary because the recurrence only ever reads dp[i-1] and dp[i-2].

INTERVIEWFollow-ups they'll ask

  • "What if the houses are in a circle?" House Robber II — first and last are adjacent, so run the linear solution twice: once on nums[0..n-2] and once on nums[1..n-1], and take the max.
  • "Can you return which houses you robbed, not just the amount?" Keep the full dp[] array and backtrack: if dp[i] === dp[i-1] you skipped house i, otherwise you robbed it (jump to i-2).
  • "What's the brute force?" Recursion with memoization (top-down DP) — try rob vs skip at each index, cache by index. Same O(n) time as bottom-up but O(n) call-stack space. The rolling version is the optimal form.
  • "What if there are negative values?" The algorithm still works correctly — Math.max will simply skip any house with a negative value since not robbing it (rob1) will always be better.
  • "What if k adjacent houses are off limits instead of just 1?" The recurrence becomes dp[i] = max(dp[i-1], dp[i-k-1] + nums[i]), and you'd need to keep a window of k+1 previous values instead of just two.

MNEMONIC The one-liner

"Skip keeps one back; rob adds two back. Shift rob2 first, then rob1."

TRIGGERS When you see ___ → reach for ___

"can't take two adjacent elements"dp[i] = max(dp[i-1], dp[i-2] + val)
maximize sum, non-adjacent picksHouse Robber rolling DP
1-D DP referencing only last 2 rowsroll to O(1) space with rob1/rob2
"circular array, same constraint"House Robber II — run twice, exclude ends

SKELETON The reusable shape

skeleton.ts
function rob(nums: number[]): number {
  let rob2 = 0; // dp[i-2]
  let rob1 = 0; // dp[i-1]

  for (const n of nums) {
    const best = Math.max(rob1, rob2 + n);
    rob2 = rob1;
    rob1 = best;
  }

  return rob1;
}

FLASHCARDS Tap to flip

What does dp[i] represent in House Robber?
The maximum loot you can rob from houses 0 through i without triggering alarms.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
For nums = [2, 7, 9, 3, 1], what is the answer?
QUESTION 02
Why does the recurrence look back two steps (dp[i-2]) instead of one?
QUESTION 03
What is the time and space complexity of the rolling-variable solution?
QUESTION 04
You write rob1 = best; rob2 = rob1; — what goes wrong?
QUESTION 05
For nums = [1, 2, 3, 1], what does the algorithm return?
QUESTION 06
Which pattern is most closely related to House Robber?
QUESTION 07
What is the minimal change needed to solve House Robber II (circular street)?