House Robber problem - Leetcode 198
A notes on the Problem #198 - House Robber on Leetcode when I practiced Dynamic Programming.
Question
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
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.
Example 1:
- Input:
nums = [1,2,3,1]
- Output:
4
- Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). Total amount you can rob = 1 + 3 = 4.
Example 2:
- Input:
nums = [2,7,9,3,1]
- Output:
12
- Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1). Total amount you can rob = 2 + 9 + 1 = 12.
Intuition
The core intuition behind this problem is:
When standing in front of a house, you have to decide whether to rob it or not. If you choose to rob it, make sure not to trigger the security system by robbing adjacent houses.
Approach
With that in mind, I explored two approaches:
- Brute-force approach
- Dynamic Programming (DP) approach
Brute-force Approach
This approach considers all valid combinations of houses that can be robbed without triggering the alarm system. To do this, I generated all binary lists of length n
, where each element is either 0
or 1
. A 1
at position i
indicates that the i-th
house is robbed, and 0
means it’s skipped. To ensure we don’t rob two adjacent houses, we filter out any combinations where two 1
s are next to each other.
Here’s the implementation for generating those valid combinations:
1
2
3
4
5
6
7
8
9
10
11
12
def generate_binary_lists(n):
result = [[0], [1]]
for _ in range(1, n):
new_result = []
for lst in result:
if lst[-1] == 1:
new_result.append(lst + [0])
else:
new_result.append(lst + [1])
new_result.append(lst + [0])
result = new_result
return result
Once we have all the valid combinations, we calculate the amount of money we can rob for each and keep track of the maximum:
1
2
3
4
5
6
7
8
9
10
11
def rob(nums):
all_binary_lists = generate_binary_lists(len(nums))
max_money = float("-inf")
current_max_robbed_house = None
for lst in all_binary_lists:
current_money = 0
for i in range(len(lst)):
current_money += lst[i] * nums[i]
if current_money > max_money:
max_money = current_money
return max_money, current_max_robbed_house
This approach worked for 47 out of 70 test cases on LeetCode. However, it failed with a Memory Limit Exceeded
error on the $48^{th}$ test case due to the exponential number of combinations.
Dynamic Programming Approach
To address the memory issue, I turned to dynamic programming.
Observation
At any house i
, you have two choices:
- Rob house
i
: You must skip housei-1
, so the total is the value at housei
plus the max value when housei-1
wasn’t robbed. - Skip house
i
: You take the max of robbing or skipping housei-1
.
Step-by-Step Example
Let’s walk through the array [2, 7, 9, 3, 1]
.
House | Value | rob (rob this) | not_rob (skip this) | Explanation |
---|---|---|---|---|
0 | 2 | 2 | 0 | Rob first house |
1 | 7 | 0 + 7 = 7 | max(0, 2) = 2 | Rob 1, skip 0; or skip 1 |
2 | 9 | 2 + 9 = 11 | max(7, 2) = 7 | Rob 2, skip 1; or skip 2 |
3 | 3 | 7 + 3 = 10 | max(11, 7) = 11 | Rob 3, skip 2; or skip 3 |
4 | 1 | 11 + 1 = 12 | max(11, 10) = 11 | Rob 4, skip 3; or skip 4 |
At the end, we return:
max(rob, not_rob) = max(12, 11) = 12
Implementation
We use two variables to keep track of:
-
rob_amount
: max money if we rob the current house. -
not_rob_amount
: max money if we skip the current house.
We update these as we iterate through the houses:
1
2
3
4
5
6
7
8
9
10
11
12
def rob(nums):
rob_amount = 0
not_rob_amount = 0
for i in range(len(nums)):
# if rob this i-th house
if i == 0:
rob_amount += nums[i]
else:
amount_if_rob = not_rob_amount + nums[i]
not_rob_amount = max(rob_amount, not_rob_amount)
rob_amount = amount_if_rob
return max(not_rob_amount, rob_amount)
This solution is efficient and passes all test cases. It runs in O(n) time and uses O(1) space.
Enjoy Reading This Article?
Here are some more articles you might like to read next: