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.
Constraints:
1 <= nums.length <= 100
0 <= nums[i] <= 400
You are a robber planning to rob houses along a street. Each house has a certain amount of money. The constraint is that you cannot rob adjacent houses because they have security systems connected. Given an integer array nums
representing the amount of money in each house, find the maximum amount of money you can rob without alerting the police.
This problem can be solved efficiently using dynamic programming. We'll explore three approaches: Memoization (top-down DP), Dynamic Programming (bottom-up DP), and a space-optimized DP approach.
This approach uses recursion with memoization to avoid redundant calculations. A recursive function dfs(i)
calculates the maximum robbery amount starting from house i
.
i
is out of bounds, return 0 (no more houses to rob).i
is the maximum of:
i
: nums[i] + dfs(i + 2)
(skip the next house due to adjacency constraint)i
: dfs(i + 1)
A memoization table (cache) stores the results of dfs(i)
to prevent recalculations.
Time Complexity: O(n) - Each house is visited at most once. Space Complexity: O(n) - For the memoization table (cache) and the recursive call stack.
class Solution:
def rob(self, nums: List[int]) -> int:
@cache
def dfs(i: int) -> int:
if i >= len(nums):
return 0
return max(nums[i] + dfs(i + 2), dfs(i + 1))
return dfs(0)
(Similar implementations can be done in Java, C++, Go, JavaScript, TypeScript, and Rust following the same logic, using their respective memoization techniques.)
This approach uses an array dp
where dp[i]
stores the maximum robbery amount considering houses up to index i
.
dp[0] = 0
, dp[1] = nums[0]
i > 1
, dp[i] = max(dp[i-1], dp[i-2] + nums[i-1])
This represents the choice between robbing or not robbing the current house.Time Complexity: O(n) - Linear iteration through the houses.
Space Complexity: O(n) - For the dp
array.
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
dp = [0] * (n + 1)
dp[1] = nums[0]
for i in range(2, n + 1):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1])
return dp[n]
(Similar implementations can be done in other languages, using their respective array structures.)
We can optimize the space complexity to O(1) by observing that we only need the values of dp[i-1]
and dp[i-2]
to calculate dp[i]
. We can maintain these two values using two variables instead of an array.
class Solution:
def rob(self, nums: List[int]) -> int:
prev1, prev2 = 0, 0
for num in nums:
prev1, prev2 = max(prev1, prev2), prev1 + num
return max(prev1, prev2)
(Similar space-optimized implementations are possible in other languages.)
All three approaches solve the House Robber problem correctly. The space-optimized DP offers the best space complexity (O(1)), making it the most efficient solution for large input arrays, while maintaining the linear time complexity of O(n). The choice of which approach to use depends on the specific constraints and preference (readability vs. space efficiency).