House Robber

At each house choose the better of robbing it (plus two back) or skipping it.

Approach

Define best(i) = max(best(i−1), best(i−2) + nums[i]) — skip this house, or take it and whatever was optimal two houses back. Roll two variables forward for O(1) space. The recurrence enforces the no-adjacent constraint automatically.

Time complexity

O(n)

Space complexity

O(1)

Common mistake

Greedily taking all even or all odd houses — the optimal choice depends on values, not parity.

Interactive walkthrough — step through it below, free and without an account

Problem

You are given an array where each value is the money available in one house along a street. Choose a set of houses to rob so that no two neighboring houses are chosen, and return the largest total amount you can collect.

Examples

Input: nums = [2, 7, 9, 3, 1]

Output: 12

Rob houses with amounts 2, 9, and 1. They are non-adjacent and total 12.

Input: nums = [5, 1, 1, 5]

Output: 10

Rob the first and last houses for a total of 10.

Problem

Problem

You are given an array where each value is the money available in one house along a street. Choose a set of houses to rob so that no two neighboring houses are chosen, and return the largest total amount you can collect.

Problem and Goal

Learning goal: Translate the no-adjacent-houses rule into a maximization problem.

Core idea Every house creates a decision: skip it or rob it. Robbing house i prevents robbing house i - 1, but it still allows every choice made through house i - 2. The goal is not to maximize the number of houses robbed; it is to maximize their total value.

What must be optimized

For nums = [2, 7, 9, 3, 1], taking houses with values 2, 9, and 1 gives 12. The tempting choice of taking the first, third, and fifth houses happens to work here, but parity is not a valid strategy in general.

Constraint translation

The rule forbids neighboring selections, so a decision to take nums[i] can safely combine only with an answer ending at i - 2 or earlier.

Algorithm plan

  • Process houses from left to right.
  • At each position, compare skipping and robbing.
  • Keep only the previous two optimal totals.
  • Return the best total after the final house.

Micro-example

  • Houses [2, 7]: skip 7 for 2, or rob 7 for 7.
  • The best total so far is 7.

Pitfalls

  • Do not assume houses with even or odd indices are best.
  • Do not add adjacent values together.
  • Remember that a house may contain 0.

Recap

  • Each house is a skip-or-take decision.
  • The no-adjacent rule determines which earlier result is compatible.
  • We seek the maximum total, not a fixed pattern of indices.

Problem and Goal

Two legal choices

At house `i`, either skip it and keep the best total through `i - 1`, or rob it and add its value to the best total through `i - 2`.

The recurrence will compare skip versus take.

Key Idea: Skip or Take

Learning goal: Understand why the recurrence captures every valid robbery plan.

Core idea Let best(i) be the maximum money obtainable from houses 0 through i. If we skip house i, the result is best(i - 1). If we rob it, house i - 1 must be skipped, so the compatible result is best(i - 2) + nums[i]. Taking the larger value gives the optimal answer for the prefix ending at i.

Recurrence

best(i) = max(best(i - 1), best(i - 2) + nums[i])

ChoiceTotalReason
Skip house ibest(i - 1)Keep the previous optimum
Rob house ibest(i - 2) + nums[i]Avoid the adjacent house

Why the recurrence is complete

Every legal solution either skips the current house or robs it. These cases are mutually exclusive and cover all possibilities, so choosing the larger total is safe.

Base cases

  • Before processing any house, the total is 0.
  • After the first house, the best total is nums[0].
  • From there, each state depends only on the previous two states.

Micro-example For house value 9, suppose the previous totals are 7 and 2. Skipping gives 7; robbing gives 2 + 9 = 11. Therefore the new best total is 11.

Pitfalls

  • best(i - 2) is required when taking a house; best(i - 1) could include its neighbor.
  • The recurrence compares totals, not individual house values.
  • A local choice that looks largest may block a better later combination.

Recap

  • best(i) describes an entire prefix.
  • The two branches are skip and take.
  • The recurrence enforces non-adjacency automatically.

Key Idea: Skip or Take

Robbing house `i` adds `nums[i]` to `best(i - 2)`.

The adjacent house is automatically excluded.

Rolling the Dynamic Program

Learning goal: Convert the two-dimensional-looking recurrence into an O(1)-space algorithm.

Core idea The recurrence refers only to the two previous results. There is no need to store the entire best array: keep the result two positions back and the result one position back, compute the new value, then shift the variables forward.

Algorithm plan

  • Initialize twoBack = 0 and oneBack = 0.
  • For each value money in nums:
    • Compute current = max(oneBack, twoBack + money).
    • Move twoBack to the old oneBack.
    • Move oneBack to current.
  • Return oneBack.
python
twoBack, oneBack = 0, 0
for money in nums:
    current = max(oneBack, twoBack + money)
    twoBack, oneBack = oneBack, current
return oneBack

State compression

The variables are not house values. They are optimal totals for two neighboring prefixes, so shifting them preserves exactly the information the recurrence needs.

Micro-example For money = 9, if twoBack = 2 and oneBack = 7, compare 7 with 2 + 9 = 11. Store 11 as the new oneBack.

Pitfalls

  • Compute current before overwriting either previous total.
  • Shift in the correct order: old oneBack becomes new twoBack.
  • Return the latest optimum, not the last house's value.

Recap

  • Only two prior DP values are needed.
  • Each loop iteration performs constant work.
  • The rolling update achieves O(n) time and O(1) extra space.

Rolling the Dynamic Program

ApproachTimeExtra spaceKey trade-off
Full DP arrayO(n)O(n)Easy to inspect every prefix
Rolling variablesO(n)O(1)Keeps only the two states needed

Walkthrough: [2, 7, 9, 3, 1]

Learning goal: Trace the rolling variables through one complete example.

Core idea We process nums = [2, 7, 9, 3, 1] from left to right. Before each iteration, twoBack represents the best total through the house two positions earlier, while oneBack represents the best total through the immediately preceding house. The new state is the better of skipping or taking the current house.

State transitions

House valueSkip: oneBackTake: twoBack + moneyNew best
2022
7277
971111
3111011
1111212
Rolling update
  1. Read the current house value.
  2. Compare skipping it with taking it plus twoBack.
  3. Save the larger total as current.
  4. Shift oneBack and twoBack forward.

Micro-example At value 3, skipping gives 11, while taking gives 7 + 3 = 10. We skip that house and keep 11. At value 1, taking gives 11 + 1 = 12.

Pitfalls

  • The best choice can switch from skip to take at any index.
  • Do not interpret twoBack as the value at an earlier house; it is an optimized prefix total.
  • Keep the example's values and update order aligned.

Recap

  • The final rolling state is 12.
  • One optimal set is houses with values 2, 9, and 1.
  • Every transition remains legal because taking uses the state two positions back.

Walkthrough: [2, 7, 9, 3, 1]

Trace the optimized DP state for nums = [2, 7, 9, 3, 1].

DP table
1 / 7
Start with zero money before the first house.
{
  "prev": [
    0,
    0
  ],
  "cur": [
    0,
    0
  ],
  "i": -1,
  "highlight": {
    "index": 0
  },
  "ruleUsed": "Initialize both rolling totals to 0",
  "keyIdea": "No houses have been considered yet.",
  "caption": "Start with zero money before the first house.",
  "delta": "Initialize twoBack = 0 and oneBack = 0.",
  "invariant": "oneBack is the best total for the processed prefix.",
  "callout": "The state stores totals, not selected indices."
}

What does dp mean?

Rule: Initialize both rolling totals to 0

Key idea

No houses have been considered yet.

The state stores totals, not selected indices.oneBack is the best total for the processed prefix.
1 / 7

Pitfalls and Edge Cases

Learning goal: Recognize why plausible shortcuts fail and how the recurrence handles boundary cases.

Core idea The optimal solution depends on the values and on combinations of houses, not on a predetermined index pattern. A parity-based strategy can miss a very valuable house, while a locally largest choice can block an even better combination. Dynamic programming evaluates both legal possibilities at every position.

The parity mistake

Greedily taking all even-indexed or all odd-indexed houses is not generally correct. For [5, 1, 1, 5], taking even indices gives 6, while taking odd indices gives 2; the true optimum is 10 by taking the first and last houses. The best set can mix positions according to values.

Common pitfall

Index parity has no relationship to value. The recurrence must compare skip and take at every house.

Edge cases

  • One house: return its value.
  • Two houses: return the larger value.
  • All zeros: return 0.
  • Repeated values: still process each position normally.

Micro-example For [5, 1, 1, 5], the final 5 is compatible with the first 5 because two houses lie between them. The answer is 10, not the sum of one fixed parity class.

Pitfalls

  • Do not always take the locally larger of two adjacent houses.
  • Do not use nums[i - 1] when taking nums[i]; use the prior optimal total.
  • Handle the empty prefix through initialization rather than invalid indexing.

Recap

  • Values determine the choices; parity does not.
  • Base initialization handles short arrays cleanly.
  • Comparing both branches prevents greedy mistakes.

Pitfalls and Edge Cases

What is the maximum amount for [5, 1, 1, 5], and why does parity fail?

Complexity and Wrap-up

Learning goal: Justify the efficiency of the rolling dynamic programming solution.

Core idea Each house is processed exactly once, and each processing step performs a constant number of arithmetic operations and comparisons. Because only twoBack, oneBack, and a temporary current value are stored, the algorithm uses constant extra memory regardless of the number of houses.

Complexity

  • Time: O(n), where n = nums.length.
  • Extra space: O(1).

Why this is optimal for the target

Reading every input value is necessary, so O(n) time is asymptotically optimal. The recurrence needs only two previous states, so O(1) extra space is sufficient.

Final implementation pattern

python
def rob(nums):
    twoBack, oneBack = 0, 0
    for money in nums:
        current = max(oneBack, twoBack + money)
        twoBack, oneBack = oneBack, current
    return oneBack

Micro-example For [2, 7, 9, 3, 1], the rolling best totals finish at 12. The algorithm never needs to remember the full table to produce that result.

Pitfalls

  • Space complexity counts extra storage, not the input array itself.
  • Do not claim O(1) time: every house must be examined.
  • Keep the state meaning consistent while shifting variables.

Recap

  • Recurrence: max(skip, take + twoBack).
  • Rolling state gives O(n) time and O(1) extra space.
  • The no-adjacent constraint is enforced by using the state two positions back.

Complexity and Wrap-up

Summarize the cost of the rolling dynamic programming algorithm.

Complexity analysis
Time
O(n)
Space
O(1)
Where the time goes
  • Scan each house onceOne constant-time recurrence evaluation per value.dominantO(n)
  • Compare skip and takeEach house uses one addition and one maximum.O(1)
  • Store rolling stateOnly two prior totals and one temporary value are retained.O(1)
How it grows
input size n →opsO(n²)O(n log n)O(n)O(log n)O(1)
Roughly what that costs
n101001,00010,000
O(n)101001K10K

The solution is optimal in time for an input that must be read and uses the minimum practical extra space.

Now try it on your own problem

Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.

Build a lesson from any problem

Related problems