The problem
Houses stand in a row and nums[i] is the money in house i (all values non-negative). You cannot rob two adjacent houses in the same night. Return the most money you can take.
Input: nums = [2, 7, 9, 3, 1]
Output: 12Rob houses 0, 2 and 4 for 2 + 9 + 1 = 12. Robbing 7 and 3 gives only 10.
How to think about it
Start with brute force. Every house is either robbed or not, so you could try all valid subsets of non-adjacent houses. That is up to O(2ⁿ) choices. Even the recursive version that decides house by house recomputes the same suffixes many times.
The key insight: decide about the current house only. Let dp[i] be the best total using houses 0..i. For house i you have exactly two options. Skip it, and the best is dp[i-1]. Rob it, and the previous house must be untouched, so the best is dp[i-2] + nums[i]. Take the larger: dp[i] = max(dp[i-1], dp[i-2] + nums[i]).
Two rolling variables. The recurrence only reaches two entries back, so keep prev = dp[i-2] and cur = dp[i-1]. Each step computes the new best and shifts. Starting both at 0 handles the first two houses without special cases.
Which pattern is this? 1-D DP with a take-or-skip choice. The tell is “pick items in a line to maximise a total, with a constraint between neighbours”. Greedy (take the biggest houses) fails because one big house can block two medium ones.
Approach
- Set
prev = 0andcur = 0(the best totals two houses back and one house back). - For each house value
n: the new best ismax(cur, prev + n). Shift:prevbecomes the oldcur,curbecomes the new best. - Return
cur.
Solution
def rob(nums):
prev, cur = 0, 0 # dp[i-2], dp[i-1]
for n in nums: prev, cur = cur, max(cur, prev + n) # skip this house, or rob it
return cur
# rob([2,7,9,3,1]) -> 12 (2 + 9 + 1)Dry run
On [2, 7, 9, 3, 1], values after processing each house:
House value n |
skip = cur |
rob = prev + n |
new prev |
new cur |
|---|---|---|---|---|
| 2 | 0 | 0 + 2 = 2 | 0 | 2 |
| 7 | 2 | 0 + 7 = 7 | 2 | 7 |
| 9 | 7 | 2 + 9 = 11 | 7 | 11 |
| 3 | 11 | 7 + 3 = 10 | 11 | 11 |
| 1 | 11 | 11 + 1 = 12 | 11 | 12 |
The final cur is 12.
Complexity
- Time: O(n). One pass over the houses.
- Space: O(1). Two variables instead of a
dparray.
Edge cases and pitfalls
- Empty list returns 0, and one house returns its value; the zero start handles both.
- Updating in the wrong order. Writing
prev = curthencur = max(cur, prev + n)on separate lines uses the newprev. The tuple assignment avoids that. - Odd/even greedy (sum every other house) fails on inputs like
[2, 1, 1, 2], where the best is 4 from the two ends. - Returning the houses chosen needs the full
dparray so you can walk back through the decisions.
Related problems
- House Robber II (LeetCode 213): the same houses arranged in a circle; run this solution twice.
- House Robber III (LeetCode 337): the take-or-skip choice on a binary tree.
- Climbing Stairs (LeetCode 70): the simplest two-rolling-variable DP.
- 1.Climbing Stairs (LeetCode 70)
- 2.Min Cost Climbing Stairs (LeetCode 746)
- 3.Maximum Subarray (LeetCode 53)
- 4.House Robber (LeetCode 198)
- 5.House Robber II (LeetCode 213)
- 6.Jump Game (LeetCode 55)
- 7.Coin Change (LeetCode 322)
- 8.Coin Change II (LeetCode 518)
- 9.Word Break (LeetCode 139)
- 10.Decode Ways (LeetCode 91)
- 11.Maximum Product Subarray (LeetCode 152)
- 12.Longest Increasing Subsequence (LeetCode 300)
- 13.Partition Equal Subset Sum (LeetCode 416)