House Robber is the gateway to one-dimensional dynamic programming. It teaches the core DP move: define a small "best so far" value for each position, then build each one from the answers you already computed.
Problem. You are a robber planning to rob houses along a street. Each house holds some money in
nums, but the alarm trips if you rob two adjacent houses on the same night. Return the maximum
money you can rob without ever robbing two neighbors.
Example: nums = [2, 7, 9, 3, 1] → answer 12 (rob houses 0, 2 and 4: 2 + 9 + 1 = 12).
The slow way first
The brute-force idea: try every valid subset of houses — every combination that never picks two neighbors — and keep the richest. There are exponentially many such subsets, so this is O(2ⁿ): hopeless for a long street.
The question to ask: standing at house i, what is the most I can carry away? It only depends on two earlier answers — the best up to the previous house, and the best up to the house two back. That overlap is exactly what dynamic programming exploits.
The idea: best up to each house
Define dp[i] = the most money robbable from houses 0..i. At house i you have two choices:
- Skip house
i— then you keep whatever was best up toi-1, which isdp[i-1]. - Rob house
i— then you cannot touchi-1, so you addnums[i]to the best up toi-2, which isdp[i-2] + nums[i].
Take the bigger of the two: dp[i] = max(dp[i-1], dp[i-2] + nums[i]).
The two base cases anchor the recurrence: dp[0] = nums[0] (one house, just rob it) and dp[1] = max(nums[0], nums[1]) (two adjacent houses, take the richer).
Walk through it
Step through the animation. The top row is nums; the bottom row is dp, filled left to right. For nums = [2, 7, 9, 3, 1]: dp[0] = 2, dp[1] = max(2, 7) = 7, dp[2] = max(7, 2 + 9) = 11, dp[3] = max(11, 7 + 3) = 11, and dp[4] = max(11, 11 + 1) = 12. The pointer i highlights the two cells each new answer is built from. The final cell, dp[4] = 12, is the answer.
Pseudocode
if the street is empty, return 0
dp[0] = nums[0] # one house: rob it
if there are at least two houses:
dp[1] = max(nums[0], nums[1]) # two adjacent: take the richer
for each house i from 2 onward:
skip = dp[i-1] # do not rob house i
rob = dp[i-2] + nums[i] # rob house i, add best up to i-2
dp[i] = max(skip, rob)
return dp[last] # best over the whole streetThe Python solution
def rob(nums):
if not nums:
return 0
dp = [0] * len(nums)
dp[0] = nums[0]
if len(nums) > 1:
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[-1]- The empty-street guard returns
0sodp[-1]is always safe. dp[i]holds the most money robbable from houses0..i.dp[0] = nums[0]anddp[1] = max(nums[0], nums[1])seed the two base cases.- Lines 8 and 9 are the heart of the algorithm: each
dp[i]is the larger of skipping (dp[i-1]) and robbing (dp[i-2] + nums[i]). dp[-1]is the best over the entire street, so we return it.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (every valid subset) | O(2ⁿ) (moderate) | exponential subsets |
| Dynamic programming (this solution) | O(n) (moderate) | one pass, O(1) work per house |
O(n) (moderate)We fill n cells, each in constant time, so the pass is O(n). The dp array costs O(n) space; since each dp[i] only needs the previous two values, you could shrink that to O(1) with two rolling variables — a common interview follow-up.
When this pattern shows up
When a problem asks for the best total under a "you cannot pick two adjacent / two in a row" rule, it is
almost always 1-D DP with the take-or-skip recurrence dp[i] = max(dp[i-1], dp[i-2] + value[i]). House
Robber, "delete and earn," and "max non-adjacent sum" are all the same move.
Get the base cases right. dp[1] is max(nums[0], nums[1]), not nums[1] — with two adjacent
houses you still take the richer one. Skipping that comparison is the classic off-by-one bug here.
Practice
For nums = [2, 7, 9, 3, 1], what is dp[2], and which two values is it the max of?
1. What does dp[i] represent?
2. What is the recurrence for dp[i] when i is at least 2?
3. Why does robbing house i use dp[i-2] rather than dp[i-1]?
4. What is the time complexity of the dp solution?