Dynamic programming · 1D
i holds nums[i] dollars. Adjacent houses have a connected security system: if you rob two houses that are next to each other, the alarm goes off. Return the maximum amount you can rob without robbing two adjacent houses.
Picture a street of houses in a row. Each house has some money inside. You may rob any houses you like, but if you rob two houses that stand next to each other, the alarm goes off. How much can you take?
The direct idea: stand at the first house and make a choice. Either rob it and jump two houses ahead (the next house is off limits), or skip it and move one house ahead. Try both, and keep the better total.
Every house splits the search in two, so n houses give about 2n paths. It also repeats itself: "what is the best I can do from house 5 onwards?" is asked again from every path that reaches house 5, and the answer is always the same.
The same small question keeps coming back: "what is the best total using only the first few houses?" So let us answer it once for each house, going from left to right, and write the answers down. If we know the best totals for the earlier houses, what is the best total up to this house?
Let dp[i] be the best total using houses 0 to i. At house i there are only two choices:
dp[i − 1].i − 1 is off limits, so we add this house's money to the best total from two houses back: dp[i − 2] + nums[i].We take the bigger of the two. That is the whole idea: dp[i] = max(dp[i − 1], dp[i − 2] + nums[i]). The first two cells are special: dp[0] is the first house, and dp[1] is the larger of the first two houses, because we can only rob one of them.
dp[0] = nums[0] and dp[1] = max(nums[0], nums[1]).i: skip = dp[i − 1] and take = dp[i − 2] + nums[i].By hand, with houses [2, 7, 9, 3, 1]:
The answer is 12: houses 0, 2 and 4. Press Play to watch the table fill, with the two values it compares at each house.
The idea. Look at what each cell reads: only the one before it and the one two before it. Older cells are never looked at again. So we do not need the whole table. Two variables are enough: prev1 (the best up to the previous house) and prev2 (the best up to the house before that).
x: the new best is max(prev1, prev2 + x). Then shift: prev2 becomes the old prev1.prev1.By hand, [2, 7, 9, 3, 1], showing (prev2, prev1) after each house: house 2 gives (0, 2). House 7 gives (2, 7). House 9 gives (7, 11). House 3 gives (11, 11). House 1 gives (11, 12). The answer is the last prev1, 12.
Cost. Same time, but only two numbers of memory.
| Approach | Time | Space |
|---|---|---|
| Try every choice | 2n | O(n) |
| A table | O(n) | O(n) |
| Two numbers | O(n) | O(1) |
These are the questions that usually come next. Open one to see an answer you could say out loud.
What they are checkingWhether you can reuse your solution on a changed problem instead of starting again. This is the well-known "House Robber II".
A strong answerNow the first and last house cannot both be robbed. So any good plan leaves out at least one of them. I run my normal solution twice: once on houses 0 to n − 2 (without the last), and once on houses 1 to n − 1 (without the first). The answer is the larger of the two. With a single house, I just return it, because there is nothing to leave out.
What they are checkingWhether you understood the two-choice idea (rob or skip), or only the list version of it.
A strong answerIt is the same two choices, but at each house in a tree. I visit the children first, then the house. Each house reports two numbers: the best total if I rob it (its money, plus the "skipped" totals of both children), and the best total if I skip it (the better of each child's two totals, added up). The answer is the larger of the two numbers at the root.
What they are checkingWhether you can read the answer back out of a table.
A strong answerI need the table, so I use Approach 1. I start at the last house. If dp[i] equals dp[i − 1], then skipping was at least as good, so I skip house i and move to i − 1. Otherwise I robbed it: I write down house i and jump to i − 2. I repeat until I run out of houses. The two-number version cannot do this, because it forgets the older cells.