House Robber

This one looks simple. A row of houses, each holds some money, grab the most you can. But there is a catch. You cannot rob two houses that sit next to each other. So the interviewer is not testing if you can add numbers. They are testing if you can spot the choice hidden at every step. At each house you decide. Do I take this one, or do I skip it? That small decision is the whole problem.

🏠 The Problem

You are given an array. Each number is the money in one house. You are a robber walking down the street. If you rob two houses that are adjacent, the alarm goes off. So you must skip at least one house between any two you rob. Find the most money you can take.

Input: [2, 7, 9, 3, 1]
Output: 12
Explanation: rob house 0 (2) + house 2 (9) + house 4 (1) = 12.
You cannot do 2 + 7 because those houses touch.

So at every house there is a yes-or-no decision. And the decisions are linked. That linking is what makes it interesting.

πŸ€” The Brute Force Idea

The first thought most people have is to try every possible set of houses that has no two neighbours. Then pick the biggest sum. You can do this with recursion. At each house you split into two paths. One path robs the house and jumps two ahead. The other path skips the house and moves one ahead.

That works, but it is slow. Each house splits into two paths. So the number of paths grows roughly like 2 to the power of n. For a long street this explodes. The same smaller streets get solved again and again. We are wasting work.

πŸ’‘ The Optimal Idea: Dynamic Programming

Here is the key thought. The most money you can rob from the first i houses depends only on smaller answers you already found. So you do not need to recompute anything. You build the answer house by house.

Stand at house i. You have two options.

  • Skip this house. Then your best is whatever you had at house i - 1.
  • Rob this house. Then you get its money plus your best from house i - 2, because house i - 1 is now off-limits.

You take the bigger of the two. In words, the recurrence is:

best[i] = max(best[i - 1], money[i] + best[i - 2])

Let us walk it on [2, 7, 9, 3, 1].

house 0: best = 2 -> 2
house 1: max(2, 7) = 7 -> 7
house 2: max(7, 2 + 9) = max(7, 11) -> 11
house 3: max(11, 7 + 3) = max(11, 10) -> 11
house 4: max(11, 11 + 1) = max(11, 12) -> 12

The answer is 12. See how each line only looks at the two lines before it?

Now notice something. To compute the answer at house i, you only ever need the last two answers. You do not need the whole table. So you can throw away everything older and keep just two rolling variables. That drops the extra memory from O(n) down to O(1).

Steps to solve it

  1. Keep two variables. prev2 is the best up to two houses back. prev1 is the best up to the house just before.
  2. Start both at 0, because before any house there is no money.
  3. Walk through each house. Compute take = money + prev2 and skip = prev1.
  4. The new best for this house is the bigger of take and skip.
  5. Slide the window forward. prev2 becomes the old prev1, and prev1 becomes the new best.
  6. After the last house, prev1 holds the answer.
house_robber.py
# Returns the most money we can rob without touching adjacent houses.
def rob(money):
prev2 = 0 # best up to two houses back
prev1 = 0 # best up to the previous house
for m in money:
take = m + prev2 # rob this house
skip = prev1 # leave this house
best = max(take, skip)
prev2 = prev1 # slide the window forward
prev1 = best
return prev1
houses = [2, 7, 9, 3, 1]
print("Max money robbed:", rob(houses))

The output of the above code will be:

Max money robbed: 12

⏱️ Time and Space Complexity

Approach Time Space
Brute force recursion (try every set) O(2ⁿ) O(n)
DP with a full table O(n) O(n)
DP with two rolling variables O(n) O(1)

Tip

In the interview, first say the recurrence out loud: best[i] = max(best[i-1], money[i] + best[i-2]). Then mention that you only ever read the last two answers, so you can replace the whole table with two variables. Saying both shows you understand the idea and you can also optimize it.

🧩 Key Takeaways

  • βœ… At every house you make one choice. Rob it and add the best from two houses back, or skip it and keep the best from the previous house.
  • βœ… The recurrence is best[i] = max(best[i - 1], money[i] + best[i - 2]).
  • βœ… You only need the last two answers, so two rolling variables are enough. That gives O(1) extra space.
  • βœ… Start both variables at 0 so the empty street and the first house work without special cases.
  • βœ… The whole thing runs in O(n) time with a single pass.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why can't the robber simply take the houses with the largest amounts?

    Why: The adjacency rule means picking one house can block a richer neighbour, so plain greedy on the biggest values fails.

  2. 2

    What is the recurrence for the best amount at house i?

    Why: You either skip house i (keep best[i-1]) or rob it (money[i] plus best[i-2], since i-1 is blocked).

  3. 3

    Why can we use just two variables instead of a full DP array?

    Why: best[i] reads only best[i-1] and best[i-2], so older values can be discarded, giving O(1) space.

  4. 4

    What is the time complexity of the rolling-variable solution?

    Why: We make one pass over the houses, doing constant work at each, so it is linear time.

πŸš€ What’s Next?

Share & Connect