Skip to main content

House Robber

How to model the choice between robbing or skipping each house while respecting the adjacency constraint.

You are a robber planning to rob houses along a street.

Each house has some amount of money, given by nums[i].

Adjacent houses cannot be robbed on the same night.

Return the maximum amount of money you can rob.

Example:

Input:  nums = [2,7,9,3,1]
Output: 12

Because the best choice is 2 + 9 + 1 = 12.

What to notice before coding

Common mistakes

Step 1 - See why local greedy reasoning fails

If you only look at the current house, you may think:

  • take the biggest one in front of you

But that breaks quickly.

The problem is not choosing one good house now.

It is choosing a good sequence with no neighbors.

Step 2 - Write the recursive baseline

For the house at index i, you have two options:

  • skip i and move to i + 1
  • rob i and move to i + 2

That creates a natural recursion.

It works, but it recomputes many indexes.

Step 3 - Build the right recurrence

Looking from left to right, the best answer up to house i is:

  • either the best answer up to i - 1
  • or the best answer up to i - 2 plus nums[i]

So:

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

Step 4 - Keep only the previous two states

You do not need a full table.

You only need to remember:

  • the best answer up to the previous house
  • the best answer up to two houses before

The interactive editor needs JavaScript. You can still read the prompt and copy the starter code below.

Starter code

export function rob(nums: number[]): number {
  // your solution here
  return 0
}
solution.ts
Stuck? Hints available.

Haven't solved it yet?

Viewing the solution now may reduce learning. It's worth trying a bit more.

Open the reference solution

Without JavaScript, the reference solution is shown inline instead of in a dialog.

Solution

Final complexity

Time: O(n)

Space: O(1)

Solution 1 - Recursion with take-or-skip before memoization

Good baseline because it makes the central decision very explicit.

export function rob(nums: number[]): number {
  function solve(index: number): number {
    if (index >= nums.length) {
      return 0
    }

    return Math.max(solve(index + 1), nums[index] + solve(index + 2))
  }

  return solve(0)
}

It works, but it wastes work by repeating the same indexes.

Solution 2 - Dynamic programming with reduced state in O(1) space

Now the same idea runs in linear time.

export function rob(nums: number[]): number {
  let twoHousesBefore = 0
  let oneHouseBefore = 0

  for (const value of nums) {
    const current = Math.max(oneHouseBefore, twoHousesBefore + value)
    twoHousesBefore = oneHouseBefore
    oneHouseBefore = current
  }

  return oneHouseBefore
}

The strong reasoning here is seeing include-or-skip as the state.

What to say in the interview

A strong short explanation would be:

For each house, I choose between skipping it and keeping the previous best answer, or robbing it and adding the best answer from two houses back. That gives the recurrence max(best(i - 1), best(i - 2) + nums[i]). Since I only depend on the previous two states, I can reduce space to O(1).

That shows that you:

  • found the right decision
  • avoided falling into local greedy reasoning
  • connected the recurrence to a space optimization

When this pattern shows up again

The same pattern comes back when you need to:

  • choose between including or excluding an item
  • handle adjacency constraints
  • build a linear dynamic programming state with two values
  • turn a decision recursion into iteration

The point is not memorizing rob.

The point is learning how to model a choice with short-range dependency.

Next challenge Search in Rotated Sorted Array Previous challenge Implement Queue using Stacks

Related challenges

Related articles