224. House Robber
Problem
Return the maximum sum obtainable from a list of nonnegative house values without choosing adjacent positions. Choosing no houses is allowed, so empty input returns zero. Values belong to distinct positions even when equal. Do not mutate nums.
Examples
Example 1
rob_houses([2,7,9,3,1])
=> 12Choosing positions 0,2,4 obeys nonadjacency.
Example 2
rob_houses([])
=> 0The empty selection has sum zero.
Example 3
rob_houses([5,1,5])
=> 10Both endpoint houses can be chosen in a line.
Reveal the worked solution and reasoning
Why this mechanism works
For the current house, every legal optimum either skips it or takes it. Skipping preserves the best answer through the previous house. Taking excludes that previous house and adds the current value to the best through two positions back. These alternatives describe complete legal selections, not just local value comparisons.
State and boundaries
previous is the best for the prefix excluding the most recent house; current is the best for the entire processed prefix.
Protocol
- Initialize both empty-prefix states to zero.
- Compute max(current,previous+value) for each house.
- Roll previous to old current and current to the new optimum.
- Return current.
Visualization
[2,7,9,3,1]
prefix best: 2,7,11,11,12
final selection indices 0,2,4 gives 2+9+1=12Python implementation
def rob_houses(nums):
previous = current = 0
for value in nums:
previous, current = current, max(current, previous + value)
return current
Worked execution
rob_houses([2,7,9,3,1])
L2 rob_houses: previous = current = 0 nums=[2, 7, 9, 3, 1]; previous=0; current=0 L3 rob_houses: for value in nums: nums=[2, 7, 9, 3, 1]; previous=0; current=0; value=2 L4 rob_houses: previous, current = current, max(current, previous + value) nums=[2, 7, 9, 3, 1]; previous=0; current=2; value=2 L3 rob_houses: for value in nums: nums=[2, 7, 9, 3, 1]; previous=0; current=2; value=7 L4 rob_houses: previous, current = current, max(current, previous + value) nums=[2, 7, 9, 3, 1]; previous=2; current=7; value=7 L3 rob_houses: for value in nums: nums=[2, 7, 9, 3, 1]; previous=2; current=7; value=9 L4 rob_houses: previous, current = current, max(current, previous + value) nums=[2, 7, 9, 3, 1]; previous=7; current=11; value=9 L3 rob_houses: for value in nums: nums=[2, 7, 9, 3, 1]; previous=7; current=11; value=3 L4 rob_houses: previous, current = current, max(current, previous + value) nums=[2, 7, 9, 3, 1]; previous=11; current=11; value=3 L3 rob_houses: for value in nums: nums=[2, 7, 9, 3, 1]; previous=11; current=11; value=1 L4 rob_houses: previous, current = current, max(current, previous + value) nums=[2, 7, 9, 3, 1]; previous=11; current=12; value=1
Result: 12. Choosing positions 0,2,4 obeys nonadjacency.
rob_houses([])
L2 rob_houses: previous = current = 0 nums=[]; previous=0; current=0
Result: 0. The empty selection has sum zero.
Why the solution is correct
Partition legal selections by whether they include the current house. The skip and take recurrences give the optimum within each class, and their maximum gives the optimum over the full prefix. The zero bases allow the first house and the empty selection correctly.
Time and space
O(n) time and O(1) auxiliary state.
Common mistakes
Choosing the locally largest house can block two smaller houses whose sum is larger. Updating previous too early can permit adjacent selections.
Reasoning card
State: previous is the best for the prefix excluding the most recent house; current is the best for the entire processed prefix.
Invariant: Best-through-current summarizes all legal selections so far.
Decision to rehearse: Compare skipping current versus taking it plus best two positions back.
Finish: Return current
Transfer exercise
Allow negative values and require at least one house. Identify which base cases and state meanings must change.