We begin by modeling the problem recursively, considering the choice to rob or skip each house.
i
, we can either rob house i
and add its value to the maximum from houses up to i-2
(skipping i-1
), or skip house i
and take the maximum from houses up to i-1
. Base cases are when i = 0
(rob only the first house) or i = 1
(rob the maximum of the first two houses).helper(i)
that computes the maximum money up to index i
.i = 0
, return nums[0]
.i = 1
, return max(nums[0], nums[1])
.max(nums[i] + helper(i-2), helper(i-1))
.helper(n-1)
, where n
is the length of nums
, to get the maximum for all houses.n
, leading to approximately 2^n operations.n
.
We optimize the recursive solution by caching results to eliminate redundant calculations.
n = 1
, return nums[0]
; if n = 2
, return max(nums[0], nums[1])
.memo
with {0: nums[0]
, 1: max(nums[0], nums[1])
} for the base cases.helper(i)
that checks if i
is in memo
. If so, return the cached value.max(nums[i] + helper(i-2), helper(i-1))
, store it in memo[i]
, and return it.helper(n-1)
to get the result.n-1
is computed once, with O(1) lookups and operations per call.memo
dictionary stores up to n
entries, and the recursion stack uses O(n) space.
We shift to an iterative approach using a dynamic programming array to compute the maximum money bottom-up.
dp[i]
stores the maximum money that can be robbed up to index i
, computed as the maximum of robbing house i
plus the maximum up to i-2
or skipping house i
and taking the maximum up to i-1
.n = 1
, return nums[0]
; if n = 2
, return max(nums[0], nums[1])
.dp
of size n
, with dp[0] = nums[0]
and dp[1] = max(nums[0], nums[1])
.i
from 2 to n-1
, set dp[i] = max(nums[i] + dp[i-2], dp[i-1])
.dp[n-1]
, the maximum for all houses.n-1
, performing O(1) operations per iteration.dp
array stores n
values.
We optimize the tabulation approach by using only two variables to track the necessary previous maximums.
dp[i]
only depends on dp[i-2]
and dp[i-1]
, we can use two variables to store these values instead of an array, updating them iteratively to compute the next maximum.n = 1
, return nums[0]
; if n = 2
, return max(nums[0], nums[1])
.prev = nums[0]
(maximum for index 0) and curr = max(nums[0], nums[1])
(maximum for index 1).i = 2
to n-1
.i
, update prev, curr = curr, max(nums[i] + prev, curr)
using simultaneous assignment.curr
, the maximum for all houses.n-1
, with O(1) operations per iteration.prev
, curr
) are used, regardless of n
.
The House Robber problem is a classic dynamic programming challenge. You're given an array of non-negative integers, where each element represents the amount of money in a house. The constraint is that you cannot rob two adjacent houses. Your task is to compute the maximum amount of money you can rob without triggering the alarm by robbing two neighboring houses.
A naive solution tries all combinations by choosing either to rob or skip each house. At every index, we make a binary choice: rob this house and skip the next, or skip this house and move to the next one. This leads to an exponential number of recursive calls. Each subproblem is recalculated multiple times, which is both inefficient and slow.
For example, if you have 10 houses, the total number of combinations to evaluate is O(2n) — which is not feasible in a real-world scenario.
The optimal solution to the House Robber problem uses a bottom-up dynamic programming approach, avoiding recomputation. The core idea is to store the maximum money that can be robbed up to each house, using only the results from the last two houses.
Here's how it works: At each step i
, the robber decides between two options:
dp[i] = max(dp[i - 1], nums[i] + dp[i - 2])
.
Since each value only depends on the two previous values, we can use two variables, prev
and curr
, instead of an entire array. This reduces space complexity from O(n) to O(1).
We initialize prev
as the value of the first house, and curr
as the max of the first two. Then for each subsequent house, we update curr
as the maximum of curr
and nums[i] + prev
, then set prev
to the previous curr
.
The House Robber problem teaches core dynamic programming principles — namely, optimal substructure and space optimization. It's an essential pattern for tackling more complex variations like "House Robber II" and "Delete and Earn."