198. House Robber
On LeetCode ->Reformulated question¶
Given a list nums, where nums[i] is the money in house i, return the maximum money you can take if you cannot rob two adjacent houses.
- Example:
- Input:
nums = [2, 7, 9, 3, 1] - Output:
12 - Best choice: rob houses with
2 + 9 + 1
Key trick¶
Use dynamic programming with only two running values:
prev2= best answer up to housei - 2prev1= best answer up to housei - 1
For each house x, the transition is:
\[
dp[i] = \max(dp[i-1], dp[i-2] + x)
\]
Trap¶
Common mistakes:
- Using recursion on slices, which is exponential and creates extra arrays.
- Forgetting that robbing the current house forces skipping the previous one.
- Overcomplicating base cases instead of using the rolling DP pattern.
Why is this question interesting?¶
It is a small but classic DP problem:
- It tests whether you can turn a choice of "take or skip" into a recurrence.
- It also rewards noticing that the full DP array is unnecessary.
Solve the problem with idiomatic python¶
class Solution:
def rob(self, nums: list[int]) -> int:
# prev2 = best total up to two houses ago
# prev1 = best total up to previous house
prev2 = 0
prev1 = 0
for money in nums:
# Either skip this house, or rob it and add prev2
prev2, prev1 = prev1, max(prev1, prev2 + money)
return prev1
Pytest test¶
import pytest
@pytest.mark.parametrize(
("nums", "expected"),
[
([1], 1),
([0], 0),
([1, 2], 2),
([2, 1], 2),
([1, 2, 3, 1], 4),
([2, 7, 9, 3, 1], 12),
([2, 1, 1, 2], 4),
([3, 1, 3, 1, 1, 3, 1], 9),
([0, 0, 0], 0),
([100, 1, 1, 100], 200),
],
)
def test_rob(nums, expected):
assert Solution().rob(nums) == expected
Comment my solution¶
Your solution should be replaced.
Problems:
- It uses recursion with slicing, which is very slow.
- Its time is exponential, and slicing adds extra \(O(n)\) overhead per call.
A good interview solution should be iterative DP in \(O(n)\) time and \(O(1)\) space.
import pytest
class Solution:
def rob(self, nums: list[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return nums[0]
mid = len(nums) // 2
skipping_mid = self.rob(nums[:mid]) + self.rob(nums[mid + 1:])
keeping_mid = self.rob(nums[:mid - 1]) + self.rob(nums[mid:])
return max(skipping_mid, keeping_mid)
@pytest.mark.parametrize(
("nums", "expected"),
[
([1,2,3,1], 4),
([2,7,9,3,1], 12),
([3,1,3,1,1,3,1], 9)
]
)
def test_rob(nums, expected):
assert Solution().rob(nums) == expected