House Robber

Problem

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police, where adjacent houses cannot be robbed on the same night.

  • 1 ≤ nums.length ≤ 100
  • 0 ≤ nums[i] ≤ 400

Example

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

The brute-force approach tries all combinations of robbing or skipping houses, which is exponential. The optimal strategy uses recursion with memoization to avoid redundant calculations. Starting from the last house (index 4), the algorithm decides whether to rob it plus the best from houses before the adjacent one (index 2), or skip it and take the best from the previous house (index 3). For example, robbing house 4 (value 1) plus dp(2) (which is 11) yields 12, while skipping house 4 and taking dp(3) (which is 9) yields 9. The maximum is 12. This decision process repeats recursively, caching results to avoid recomputation.

Approach

Straightforward Solution

A brute-force recursive approach tries all subsets of houses to rob, leading to exponential time complexity due to repeated calculations of the same subproblems.

Core Observation

The maximum amount that can be robbed up to house i depends only on the decisions made for houses before i, specifically whether house i-1 was robbed or not, creating overlapping subproblems with optimal substructure.

Path to Optimal

Recognizing the overlapping subproblems and optimal substructure leads to a dynamic programming solution. By defining dp(i) as the maximum amount that can be robbed from houses 0 to i, the problem breaks down into choosing between robbing house i plus dp(i-2), or skipping house i and taking dp(i-1). Memoization caches these results, reducing time complexity from exponential to linear.

Optimal Approach

Implement a top-down recursive function with memoization that returns dp(i), the maximum amount that can be robbed up to house i. The function returns 0 for negative indices (base case). For each i, it computes the maximum of robbing house i plus dp(i-2) or skipping house i and taking dp(i-1). Memoization ensures each state is computed once, resulting in O(n) time and space complexity.

Time

O(n)

Each house index from 0 to n-1 is computed once and stored in the memo dictionary, avoiding repeated calculations.

Space

O(n)

The memo dictionary stores results for each house index, and the recursion stack can go up to O(n) in the worst case.

Pattern Spotlight

Dynamic Programming (Top-Down Memoization)

When a problem involves making a sequence of decisions with overlapping subproblems and optimal substructure, define a recursive state representing the problem up to a certain index, then cache results to avoid redundant work and achieve linear time.

Solution

1class Solution:
2 def rob(self, nums: list[int]) -> int:
3 memo = {}
4
5 def dp(i):
6 if i < 0:
7 return 0
8 if i in memo:
9 return memo[i]
10
11 result = max(nums[i] + dp(i - 2), dp(i - 1))
12 memo[i] = result
13 return result
14
15 return dp(len(nums) - 1)

Step-by-Step Solution

1

Initialize Memoization Cache for Subproblem Results

3memo = {}

Objective

To create a dictionary that stores the maximum robbery amount for each house index to avoid redundant recursive computations.

Key Insight

Memoization transforms the naive exponential recursion into a linear time algorithm by caching results of subproblems. This cache acts as a memory of previously solved states, ensuring each state is computed once and reused whenever needed.

Interview Quick-Check

Core Logic

Memoization stores dp(i) results, preventing repeated calculations of the same subproblems.

Common Pitfalls & Bugs

Forgetting to memoize results leads to exponential time due to repeated recursive calls.

2

Define Recursive Function to Compute Maximum Robbery Up to Index i

5def dp(i):
6 if i < 0:
7 return 0
8 if i in memo:
9 return memo[i]
11 result = max(nums[i] + dp(i - 2), dp(i - 1))
12 memo[i] = result
13 return result

Objective

To recursively compute the maximum amount that can be robbed from houses 0 to i, using memoization to cache results.

Key Insight

The recursive function dp(i) embodies the problem's optimal substructure by deciding whether to rob house i plus dp(i-2) or skip house i and take dp(i-1). The base case returns 0 for invalid indices, ensuring recursion termination. Memoization ensures each state is computed once, converting exponential recursion into linear time.

Interview Quick-Check

Core Logic

The recurrence dp(i) = max(nums[i] + dp(i-2), dp(i-1)) captures the choice between robbing or skipping house i.

State & Boundaries

Base case dp(i) = 0 for i < 0 prevents invalid indices and terminates recursion.

Common Pitfalls & Bugs

Not checking the memo before recursion causes redundant computations and timeouts.

3

Return Maximum Robbery Amount for All Houses

15return dp(len(nums) - 1)

Objective

To initiate the recursive computation from the last house and return the final maximum robbery amount.

Key Insight

Starting the recursion from the last house index ensures the entire problem is solved. The recursive calls break down the problem into smaller subproblems, and memoization guarantees efficiency. The returned value represents the maximum amount that can be robbed without alerting the police.

Interview Quick-Check

Core Logic

Calling dp(len(nums) - 1) computes the maximum robbery amount considering all houses.

Line Analysis

This solution has 3 Critical lines interviewers watch for.

Line 9 Critical
return memo[i]

Compute the maximum robbery amount by choosing to rob house i plus dp(i-2) or skip house i and take dp(i-1).

This recurrence captures the core decision: robbing house i excludes adjacent house i-1, so the next valid house is i-2; skipping house i allows considering house i-1. Taking the max ensures optimality.

Line 6 Critical
if i < 0:

Return 0 if the index i is negative, representing no houses left to rob.

This base case prevents invalid indices and terminates recursion, ensuring correctness and preventing infinite recursion.

Line 7 Critical
return 0

Check if the result for index i is already computed and cached.

This memoization check avoids recomputing dp(i), which is critical for achieving linear time complexity.

Line 3 Core
memo = {}

Initialize a memoization dictionary to cache results of subproblems.

This dictionary is essential to store computed dp(i) values, preventing redundant recursive calls and reducing time complexity from exponential to linear.

Line 8 Core
if i in memo:

Return the cached result for index i if available.

Returning the cached value immediately prunes the recursion tree, preventing exponential blowup.

Line 11 Core
result = max(nums[i] + dp(i - 2), dp(i - 1))

Store the computed result in the memo dictionary for index i.

Caching the result ensures that future calls to dp(i) return instantly, enabling efficient memoized recursion.

Line 15 Core
return dp(len(nums) - 1)

Start the recursion from the last house and return the maximum robbery amount.

This call triggers the entire memoized recursion, solving the problem for all houses and returning the final answer.

Line 5 Core
def dp(i):

Define the recursive function dp that computes the maximum robbery amount up to house i.

Encapsulating the problem in a recursive function with parameter i allows breaking down the problem into smaller subproblems with overlapping states.

Line 12 Core
memo[i] = result

Return the computed maximum robbery amount for index i.

Returning the result completes the recursive call, propagating the optimal solution up the call stack.

Test Your Understanding

Why does the recursive function consider dp(i - 2) when deciding to rob house i?

Because robbing house i prohibits robbing the adjacent house i-1, so the next valid house to consider is i-2, ensuring no two adjacent houses are robbed.

Related Problems

Dynamic Programming pattern

Don't just read it. Drill it.

Reconstruct House Robber from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the House Robber drill

or drill a free problem