[Medium] 198. House Robber
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.
Examples
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 <= 1000 <= nums[i] <= 400
Thinking Process
- DP State Definition:
dp[i]represents the maximum money robbed up to housei-1(1-indexed)dp[i-1] + nums[i]: Rob current house (can’t rob previous)dp[i]: Skip current house (keep previous maximum)dp[0] = 0(no houses)
- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
- Base cases first; optimize space if only prior row/layer is needed.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| 1D DP (this problem) | O(n) | O(n) or O(1) | Linear recurrence |
| 2D DP | O(nm) | O(nm) or O(n) | Grid or two-sequence problems |
| State machine DP | O(n) | O(1) | Buy/sell, hold/not-hold states |
| Memoization (top-down) | Same as DP | O(n) | Recursive + cache |
Solution
Time Complexity: O(n) - Single pass through the array
Space Complexity: O(n) - DP array (can be optimized to O(1))
This is a classic dynamic programming problem. The key insight is that for each house, we have two choices:
- Rob it: Add its value to the maximum from two houses ago
- Skip it: Take the maximum from the previous house
Solution: DP Array Approach
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() <= 0) return 0;
if(nums.size() == 1) return nums[0];
vector<int> dp(nums.size() + 1);
dp[0] = 0;
dp[1] = nums[0];
for(int i = 1; i < (int)nums.size(); i++) {
dp[i + 1] = max(dp[i - 1] + nums[i], dp[i]);
}
return dp[nums.size()];
}
};
Solution Explanation
Approach: 1D DP (this problem)
Key idea: 1. DP State Definition: dp[i] represents the maximum money robbed up to house i-1 (1-indexed)
How the code works:
- DP State Definition:
dp[i]represents the maximum money robbed up to housei-1(1-indexed)dp[i-1] + nums[i]: Rob current house (can’t rob previous)dp[i]: Skip current house (keep previous maximum)dp[0] = 0(no houses)- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
Walkthrough — input nums = [1,2,3,1], expected output 4:
Rob house 1 (money = 1) and then rob house 3 (money = 3). Total amount you can rob = 1 + 3 = 4.
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | DP Array (1-indexed) | O(n) | O(n) | Clear indexing, easy to understand | O(n) space | | Space-Optimized | O(n) | O(1) | Optimal space usage | Can’t trace path | | DP Array (0-indexed) | O(n) | O(n) | Standard DP pattern | Requires base case handling |
Algorithm Breakdown
int rob(vector<int>& nums) {
// Edge cases
if(nums.size() <= 0) return 0;
if(nums.size() == 1) return nums[0];
// DP array: dp[i] = max money up to house i-1
vector<int> dp(nums.size() + 1);
dp[0] = 0; // No houses
dp[1] = nums[0]; // Only first house
// For each house starting from index 1
for(int i = 1; i < (int)nums.size(); i++) {
// Choose: rob current house OR skip it
dp[i + 1] = max(
dp[i - 1] + nums[i], // Rob current (skip previous)
dp[i] // Skip current (keep previous max)
);
}
return dp[nums.size()]; // Maximum for all houses
}
Complexity
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | DP Array (1-indexed) | O(n) | O(n) | Clear indexing, easy to understand | O(n) space | | Space-Optimized | O(n) | O(1) | Optimal space usage | Can’t trace path | | DP Array (0-indexed) | O(n) | O(n) | Standard DP pattern | Requires base case handling |
Recurrence Relation Explanation
The core recurrence relation is:
dp[i+1] = max(dp[i-1] + nums[i], dp[i])
Why this works:
dp[i-1] + nums[i]: If we rob housei, we can’t rob housei-1, so we take the maximum fromi-2(stored indp[i-1]) and add current house valuedp[i]: If we skip housei, we keep the maximum from all previous houses up toi-1
This ensures we never rob two adjacent houses while maximizing the total amount.
Implementation Details
1-Indexed vs 0-Indexed
1-Indexed (Your Solution):
dp[0] = 0; // No houses
dp[1] = nums[0]; // First house
dp[i+1] = max(...); // Current house at index i
0-Indexed (Standard):
dp[0] = nums[0]; // First house
dp[1] = max(nums[0], nums[1]); // First two houses
dp[i] = max(...); // Current house at index i
Both approaches are correct; 1-indexed makes base cases simpler.
Type Casting
for(int i = 1; i < (int)nums.size(); i++)
The (int) cast prevents comparison warnings between int and size_t. Alternatively:
for(size_t i = 1; i < nums.size(); i++)
Common Mistakes
- Empty array:
nums = []→ return0 - Single house:
nums = [5]→ return5 - Two houses:
nums = [2,1]→ returnmax(2,1) = 2 - All zeros:
nums = [0,0,0]→ return0 -
Alternating pattern:
nums = [1,2,1,2]→ returnmax(1+1, 2+2) = 4 - Forgetting edge cases: Empty array or single element
- Wrong recurrence: Using
dp[i-2]instead ofdp[i-1]for 1-indexed - Index out of bounds: Not handling base cases properly
- Wrong return value: Returning
dp[nums.size()-1]instead ofdp[nums.size()]for 1-indexed - Not considering skip option: Only considering robbing current house
Optimization Tips
- Space Optimization: Use two variables instead of array for O(1) space
- Early Termination: Can add checks for special cases
- Memoization: For recursive approach, use memoization to avoid recomputation
- Bottom-Up DP: Preferred over top-down for better cache performance
Related Problems
- 213. House Robber II - Houses arranged in a circle
- 337. House Robber III - Binary tree structure
- 740. Delete and Earn - Similar DP pattern
- 1980. Find Unique Binary String - Different problem but similar constraint pattern
Real-World Applications
- Resource Allocation: Maximizing profit with constraints
- Scheduling: Selecting non-overlapping intervals with maximum value
- Network Optimization: Routing with constraints
- Game Theory: Optimal strategy selection
- Financial Planning: Investment decisions with restrictions
Pattern Recognition
This problem follows the “Pick or Skip” DP pattern:
For each element:
Option 1: Pick it (with constraints)
Option 2: Skip it
Choose the option that maximizes/minimizes the objective
Similar problems:
- Maximum Subarray (Kadane’s algorithm)
- Climbing Stairs
- Coin Change
- Knapsack problems
This problem is a fundamental introduction to dynamic programming, teaching the “pick or skip” decision pattern that appears in many optimization problems.
Key Takeaways
- DP State Definition:
dp[i]represents the maximum money robbed up to housei-1(1-indexed) - Recurrence Relation:
dp[i+1] = max(dp[i-1] + nums[i], dp[i])dp[i-1] + nums[i]: Rob current house (can’t rob previous)dp[i]: Skip current house (keep previous maximum)
- Base Cases:
dp[0] = 0(no houses)dp[1] = nums[0](only first house)
- 1-Indexed DP Array: Using
dp[i+1]makes indexing cleaner and avoids edge cases
References
- LC 198: House Robber on LeetCode
- LeetCode Discuss — LC 198: House Robber
- LeetCode Editorial (may require premium)