House Robber
For each house, the best total either skips it or takes it on top of the best from two houses back, so walk the street keeping two numbers. O(n) time and O(1) space, with a step-through, code in five languages and the Big O worked out from the code.
The problem
Section titled “The problem”Houses stand in a row, and nums[i] is the money in house i. You may take the money from any houses
you like, except that you can never take from two houses next to each other. Return the most money
you can take.
Example 1
Input: nums = [3, 1, 4, 9]
Output: 12
Take houses 0 and 3: 3 + 9 = 12. Taking every other house gives only 3 + 4 = 7 or 1 + 9 = 10.
Example 2
Input: nums = [5]
Output: 5
One house, so take it.
Example 3
Input: nums = [4, 1, 1, 4]
Output: 8
Take the first and the last house. They are not neighbors.
Constraints
Section titled “Constraints”- 1 ≤ nums.length ≤ 100 (call it n)
- 0 ≤ nums[i] ≤ 400
Clarifying questions
Section titled “Clarifying questions”- Can we skip two or more houses in a row?
- Yes. The only rule is never two neighbors. Example 1 skips two houses.
- Do the first and last house count as neighbors?
- No, the street is a straight line. (House Robber II makes it a circle.)
- Can a house hold no money?
- Yes, 0 is allowed. No amount is negative, so taking a house never lowers the total by itself.
- Do we return the houses or the total?
- Just the total.
Spot the pattern
Section titled “Spot the pattern”Look at the last house. Either we skip it, and the answer is the best for the houses before it. Or we take it, so its neighbor is off limits, and the answer is its money plus the best for the houses two back. Each answer comes from answers to smaller streets, and those repeat. That is dynamic programming.
Solutions
Section titled “Solutions”Plain recursion O(2ⁿ)
The idea
Section titled “The idea”Let best(house) be the most money from houses 0 to house. For that house there are two choices:
- Skip it:
best(house - 1). - Take it:
nums[house] + best(house - 2), since the house just before it must then be skipped.
best(house) is the bigger of the two. When there are no houses, house < 0, the best is 0. The answer is
best(len(nums) - 1).
Why it is correct: every allowed choice of houses either includes the last house or does not. The two options cover both cases, and each one is solved for the best possible choice of the rest.
Walkthrough
Section titled “Walkthrough”- def best(house):
- # most money from houses 0 .. house
- if house < 0: return 0
- return max(best(house - 1), nums[house] + best(house - 2))
best(house) is the most we can take from houses 0 to house. For that house, either skip it, best(house − 1), or take it and skip its neighbor, nums[house] + best(house − 2). The picture is every call best(3) makes.
class Solution { private int[] nums;
// Plain recursion: the best haul from houses 0 to i either skips house i // or takes it and skips house i - 1. The same smaller cases are solved again and again. public int rob(int[] nums) { this.nums = nums; return best(nums.length - 1); }
private int best(int house) { // No houses left: nothing to take. if (house < 0) return 0; return Math.max(best(house - 1), nums[house] + best(house - 2)); }}# Plain recursion: the best haul from houses 0 to i either skips house i# or takes it and skips house i - 1. The same smaller cases are solved again and again.def rob(nums: list[int]) -> int: def best(house: int) -> int: # No houses left: nothing to take. if house < 0: return 0 return max(best(house - 1), nums[house] + best(house - 2))
return best(len(nums) - 1)public class Solution{ private int[] nums;
// Plain recursion: the best haul from houses 0 to i either skips house i // or takes it and skips house i - 1. The same smaller cases are solved again and again. public int Rob(int[] nums) { this.nums = nums; return Best(nums.Length - 1); }
private int Best(int house) { // No houses left: nothing to take. if (house < 0) return 0; return System.Math.Max(Best(house - 1), nums[house] + Best(house - 2)); }}// Plain recursion: the best haul from houses 0 to i either skips house i// or takes it and skips house i - 1. The same smaller cases are solved again and again.func rob(nums []int) int { var best func(house int) int best = func(house int) int { // No houses left: nothing to take. if house < 0 { return 0 } return max(best(house-1), nums[house]+best(house-2)) } return best(len(nums) - 1)}// Plain recursion: the best haul from houses 0 to i either skips house i// or takes it and skips house i - 1. The same smaller cases are solved again and again.function rob(nums: number[]): number { const best = (house: number): number => { // No houses left: nothing to take. if (house < 0) return 0; return Math.max(best(house - 1), nums[house] + best(house - 2)); }; return best(nums.length - 1);}1 Find the work that repeats
The work repeats through recursion, so we count the calls of
best. Each call does one check, an addition and a max, a fixed job, so each call counts as 1 step.- def best(house):
- # most money from houses 0 .. house
- if house < 0: return 0
- return max(best(house - 1), nums[house] + best(house - 2))
2 Count the runs on Example 1
The table counts how often each
best(house)was called. The smaller the street, the more often it is asked for: best(0) three times.1 + 1 + 2 + 3 + 5 + 3 = 15 calls.
call best(3) best(2) best(1) best(0) best(-1) best(-2) times called 1 1 2 3 5 3 total1 + 1 + 2 + 3 + 5 + 3 = 15 calls3 Write the count for any n
Each call makes 2 more calls, and each lowers i by at least 1. Starting from i = n − 1, the calls reach i = −1 or −2, so the tree is at most n + 1 levels deep, and level d holds at most 2ᵈ calls:
1 + 2 + 4 + … + 2ⁿ = 2ⁿ⁺¹ − 1 callsCheck it with Example 1: 2⁵ − 1 = 31 at most. The real run made 15, because the take side of the tree is shorter.
4 Keep only the part that grows fastest
Drop the 2 and the − 1. What is left is 2ⁿ. With 100 houses, that is far too many.
So the time is O(2ⁿ).
S Space: count the extra memory the same way
The call stack holds one path of the tree at a time, at most n + 1 calls. So the space is O(n).
call stack (up to n + 1 deep)
Table O(n)
The idea
Section titled “The idea”best(house) only ever needs best(house - 1) and best(house - 2). So fill in the answers from the left, in a
table, and each one finds what it needs already there.
To avoid special cases for the first two houses, put two extra 0 slots in front: slot i + 2 holds the
answer for houses 0 to i. Then for each house, best[i + 2] = max(best[i + 1], nums[i] + best[i]).
Why it is correct: this is the same rule as the recursion, worked out once per house, in an order where both answers it needs are always filled in.
Walkthrough
Section titled “Walkthrough”- best = [0, 0] + [0] * nums_length # slot i + 2 is houses 0 .. i
- for i in 0 .. nums_length - 1:
- best[i + 2] = max(best[i + 1], nums[i] + best[i])
- return best[nums_length + 1]
Fill a table instead. Slot i + 2 holds the best total for houses 0 to i. The first two slots stand for "no houses yet", worth 0, so every house has two slots behind it.
class Solution { // Table, bottom up: best[i + 2] is the most we can take from houses 0 to i. // Fill it left to right, so both smaller answers are ready. O(n) time and space. public int rob(int[] nums) { int numsLength = nums.length; // Two extra slots in front stand for "no houses", worth 0. int[] best = new int[numsLength + 2]; for (int i = 0; i < numsLength; i++) { // Skip house i, or take it on top of the best from two houses back. best[i + 2] = Math.max(best[i + 1], nums[i] + best[i]); } return best[numsLength + 1]; }}# Table, bottom up: best[i + 2] is the most we can take from houses 0 to i.# Fill it left to right, so both smaller answers are ready. O(n) time and space.def rob(nums: list[int]) -> int: nums_length = len(nums) # Two extra slots in front stand for "no houses", worth 0. best = [0] * (nums_length + 2) for i in range(nums_length): # Skip house i, or take it on top of the best from two houses back. best[i + 2] = max(best[i + 1], nums[i] + best[i]) return best[nums_length + 1]public class Solution{ // Table, bottom up: best[i + 2] is the most we can take from houses 0 to i. // Fill it left to right, so both smaller answers are ready. O(n) time and space. public int Rob(int[] nums) { int numsLength = nums.Length; // Two extra slots in front stand for "no houses", worth 0. var best = new int[numsLength + 2]; for (int i = 0; i < numsLength; i++) { // Skip house i, or take it on top of the best from two houses back. best[i + 2] = System.Math.Max(best[i + 1], nums[i] + best[i]); } return best[numsLength + 1]; }}// Table, bottom up: best[i + 2] is the most we can take from houses 0 to i.// Fill it left to right, so both smaller answers are ready. O(n) time and space.func rob(nums []int) int { numsLength := len(nums) // Two extra slots in front stand for "no houses", worth 0. best := make([]int, numsLength+2) for i := 0; i < numsLength; i++ { // Skip house i, or take it on top of the best from two houses back. best[i+2] = max(best[i+1], nums[i]+best[i]) } return best[numsLength+1]}// Table, bottom up: best[i + 2] is the most we can take from houses 0 to i.// Fill it left to right, so both smaller answers are ready. O(n) time and space.function rob(nums: number[]): number { const numsLength = nums.length; // Two extra slots in front stand for "no houses", worth 0. const best = new Array<number>(numsLength + 2).fill(0); for (let i = 0; i < numsLength; i++) { // Skip house i, or take it on top of the best from two houses back. best[i + 2] = Math.max(best[i + 1], nums[i] + best[i]); } return best[numsLength + 1];}1 Find the work that repeats
The loop body does one addition, one max and one write. That is a fixed job, so each run of the loop counts as 1 step.
- best = [0, 0] + [0] * nums_length # slot i + 2 is houses 0 .. i
- for i in 0 .. nums_length - 1:
- best[i + 2] = max(best[i + 1], nums[i] + best[i])
- return best[nums_length + 1]
2 Count the runs on Example 1
The loop runs once per house.
4 runs.
house 0 1 2 3 loop runs 1 1 1 1 total4 runs3 Write the count for any n
The loop goes over every house once:
n runsCheck it with Example 1: n = 4 runs. That matches the count.
4 Keep only the part that grows fastest
Nothing to drop. It is O(n).
So the time is O(n).
S Space: count the extra memory the same way
The table has n + 2 slots. So the space is O(n).
best table (n + 2)
Two numbers O(n)
The idea
Section titled “The idea”The table is read only two slots back. So keep just those two answers:
two_back: the best total up to two houses ago.one_back: the best total up to the last house.
For each house, the new best is max(one_back, money + two_back). Then slide: two_back takes the old
one_back, and one_back takes the new best.
Why it is correct: the two numbers hold exactly the two table slots the rule needs, so it computes the same answers as the table.
Walkthrough
Section titled “Walkthrough”- two_back, one_back = 0, 0
- for money in nums:
- take = money + two_back
- two_back, one_back = one_back, max(one_back, take)
- return one_back
The table only ever reads the two slots behind the one being filled. So keep just two numbers: two_back and one_back, both 0 before any house.
class Solution { // Two numbers: each answer needs only the two before it, so keep just those // while walking left to right. O(n) time, O(1) space. public int rob(int[] nums) { // Best so far up to two houses back, and up to the last house. int twoBack = 0, oneBack = 0; for (int money : nums) { // Skip this house, or take it on top of the best from two houses back. int next = Math.max(oneBack, money + twoBack); twoBack = oneBack; oneBack = next; } return oneBack; }}# Two numbers: each answer needs only the two before it, so keep just those# while walking left to right. O(n) time, O(1) space.def rob(nums: list[int]) -> int: # Best so far up to two houses back, and up to the last house. two_back, one_back = 0, 0 for money in nums: # Skip this house, or take it on top of the best from two houses back. two_back, one_back = one_back, max(one_back, money + two_back) return one_backpublic class Solution{ // Two numbers: each answer needs only the two before it, so keep just those // while walking left to right. O(n) time, O(1) space. public int Rob(int[] nums) { // Best so far up to two houses back, and up to the last house. int twoBack = 0, oneBack = 0; foreach (int money in nums) { // Skip this house, or take it on top of the best from two houses back. int next = System.Math.Max(oneBack, money + twoBack); twoBack = oneBack; oneBack = next; } return oneBack; }}// Two numbers: each answer needs only the two before it, so keep just those// while walking left to right. O(n) time, O(1) space.func rob(nums []int) int { // Best so far up to two houses back, and up to the last house. twoBack, oneBack := 0, 0 for _, money := range nums { // Skip this house, or take it on top of the best from two houses back. twoBack, oneBack = oneBack, max(oneBack, money+twoBack) } return oneBack}// Two numbers: each answer needs only the two before it, so keep just those// while walking left to right. O(n) time, O(1) space.function rob(nums: number[]): number { // Best so far up to two houses back, and up to the last house. let twoBack = 0; let oneBack = 0; for (const money of nums) { // Skip this house, or take it on top of the best from two houses back. [twoBack, oneBack] = [oneBack, Math.max(oneBack, money + twoBack)]; } return oneBack;}1 Find the work that repeats
The loop body does one addition, one max and two moves. That is a fixed job, so each run of the loop counts as 1 step.
- two_back, one_back = 0, 0
- for money in nums:
- take = money + two_back
- two_back, one_back = one_back, max(one_back, take)
- return one_back
2 Count the runs on Example 1
The loop runs once per house.
4 runs.
house 0 1 2 3 loop runs 1 1 1 1 total4 runs3 Write the count for any n
n runsCheck it with Example 1: n = 4. That matches the count.
4 Keep only the part that grows fastest
Nothing to drop. It is O(n).
So the time is O(n).
S Space: count the extra memory the same way
Only
two_backandone_backare kept, whatever n is. So the space is O(1).two_backone_back
Tested at the limits
Section titled “Tested at the limits”2,000 random streets of 1 to 14 houses with amounts 0 to 5, so ties between taking and skipping a house are common. The plain recursion, the table and the two numbers gave the same answer every time.
| Largest inputs | Result |
|---|---|
| 100 houses (the most allowed), each with up to 400. Two numbers. | Under 0.001 s, and under 0.001 MB of extra memory: just the two numbers. |
| 25 houses. Plain recursion. | 0.02 s. Each extra house makes about 1.6 times more calls, so 100 houses is far out of reach. |
The designbeyondcode digest, every Saturday
New deep dives, interview tips and site news in one short email. Free. Leave with one click.
We never sell or share your email. We only use it to send this newsletter.