House Robber

A row of houses lines a street, and nums[i] is the amount of cash stored in house i. You may take the cash from any set of houses, with one restriction: you can never take from two houses that are next to each other, because adjacent houses share an alarm.

Return the largest total amount you can collect.

Example 1

Input: nums = [3,1,9,2,4] Output: 16 Why: Take houses 0, 2 and 4: 3 + 9 + 4 = 16. Houses 1 and 3 are skipped.

Example 2

Input: nums = [12,1,2,11] Output: 23 Why: Skipping two houses in a row is sometimes best: houses 0 and 3 give 12 + 11 = 23.

Example 3

Input: nums = [7] Output: 7 Why: With one house, take it.

Constraints

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

Pattern Path · Dynamic Programming · problem 153 of 169

Greedy choices such as "take every other house" fail. Think about the last house instead: is it taken or not?
If house i is taken, house i - 1 is not, so the best total is nums[i] plus the best for the first i - 1 houses. If it is skipped, the best total is the best for the first i houses.
Let best[i] be the answer for houses 0..i. Then best[i] = max(best[i-1], best[i-2] + nums[i]), and you only need the last two values.
Target: O(n) time, O(1) extra space.

Start simple

Try every subset of houses with no two adjacent (recursively take-or-skip each house): O(2n) time.

The key idea

Look at the problem from the end. For the first i + 1 houses, the best plan either skips house i or takes it.

  • If it skips house i, what remains is exactly the same problem for houses 0..i-1.
  • If it takes house i, then house i - 1 must be skipped, so the rest is the same problem for houses 0..i-2, plus nums[i].

So best(i) = max(best(i-1), best(i-2) + nums[i]). This recurrence has optimal substructure (the best plan contains a best plan for a shorter street) and overlapping subproblems (plain recursion would recompute best for the same prefix again and again, which is exponential).

Compute it from left to right instead. Each value depends only on the previous two, so two variables are enough: prev2 for the best up to two houses back and prev1 for the best up to the previous house. For each house, compute the new best and shift the pair along. Starting both at 0 handles the first two houses without special cases.

Complexity

  • Time: O(n)
  • Space: O(1)

Python

class Solution:
    def rob(self, nums: List[int]) -> int:
        prev2, prev1 = 0, 0  # best totals up to two houses back / up to the previous house
        for cash in nums:
            prev2, prev1 = prev1, max(prev1, prev2 + cash)
        return prev1

JavaScript

var rob = function(nums) {
  let prev2 = 0; // best total up to two houses back
  let prev1 = 0; // best total up to the previous house
  for (const cash of nums) {
    const best = Math.max(prev1, prev2 + cash);
    prev2 = prev1;
    prev1 = best;
  }
  return prev1;
};

What the interviewer is checking

This is a gateway dynamic programming question: the interviewer wants to hear you define the state, derive the take-or-skip recurrence and then reduce memory to two variables.

Mistakes to avoid

  • Assuming the answer is the larger of the even-index sum and the odd-index sum; skipping two houses in a row can be better.
  • Index errors for one or two houses when using a dp array with i - 2.
  • Returning best[n - 2] or mixing up which of the rolling variables is which after the loop.

Follow-ups to expect

  • What if the houses form a circle? (Run twice: exclude first, then exclude last.)
  • Return which houses to take? (Keep the dp array and backtrack.)
  • What if houses form a binary tree? (Return a take/skip pair per subtree.)

In real software

Choosing non-conflicting time slots or ads where neighbours interfere, and maximum-weight independent set on a path in scheduling tools, use this same take-or-skip recurrence.

Practice next

Next Post Previous Post
No Comment
Add Comment
comment url