DSAPrep
Medium1-D DP

House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, but adjacent houses have security systems connected together -- if you break into two adjacent houses on the same night, the police get called.

Given an integer array nums representing the amount of money at each house, return the maximum amount you can rob tonight without alerting the police.

Example 1

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and house 3 (money = 3). Total = 1 + 3 = 4.

Example 2

Input: nums = [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (2), house 3 (9), and house 5 (1). Total = 2 + 9 + 1 = 12.

Constraints

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400
View original on LeetCode ↗

At every house i you face a binary choice: rob it (and add its money to whatever the best haul was up through house i-2, since i-1 is now off-limits) or skip it (and keep whatever the best haul was through i-1). Taking the better of those two options at each step is the entire algorithm.

Brute Force Recursion

Time O(2ⁿ)Space O(n)

Define helper(i) as the max money obtainable using only houses 0..i. At house i, either skip it (helper(i-1)) or rob it and add to the best from two houses back (helper(i-2) + nums[i]).

class Solution:
def rob(self, nums: list[int]) -> int:
def helper(i: int) -> int:
if i < 0:
return 0
return max(helper(i - 1), helper(i - 2) + nums[i])
return helper(len(nums) - 1)

Each call spawns two more, so the call tree doubles with depth → O(2ⁿ) time, even though only n distinct values of i ever occur (huge redundant recomputation).

Top-Down Memoization

Time O(n)Space O(n)

Cache helper(i) so each of the n distinct subproblems is solved exactly once.

class Solution:
def rob(self, nums: list[int]) -> int:
memo = {}
def helper(i: int) -> int:
if i < 0:
return 0
if i in memo:
return memo[i]
memo[i] = max(helper(i - 1), helper(i - 2) + nums[i])
return memo[i]
return helper(len(nums) - 1)

n subproblems, O(1) work each beyond memoized recursive calls → O(n) time, O(n) space for the memo and call stack.

Bottom-Up DP

OptimalTime O(n)Space O(n)

Build dp[i] = max money robbable from houses 0..i, filled left to right: dp[i] = max(dp[i-1], dp[i-2] + nums[i]).

class Solution:
def rob(self, nums: list[int]) -> int:
n = len(nums)
if n == 1:
return nums[0]
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[-1]

Trace for nums = [2, 7, 9, 3, 1]:

2
0
·
1
·
2
·
3
·
4
1 / 5
comparingresult

dp[0] = nums[0] = 2. Only one house, so rob it.

One pass, constant work per house → O(n) time. Since dp[i] only ever needs the previous two entries, the array can be collapsed to two rolling variables for O(1) space, but the full array is shown here for clarity.