Editorial
Core insight
House Robber · Dynamic Programming (DP) Patterns
Core Insight for House Robber
The key intuition is that to maximize profit at house , we only need to know the maximum profit possible up to house and the maximum profit possible up to house . We do not need to know the specific combination of houses robbed to achieve those totals.
Let represent the maximum money we can rob from the first houses (indices to ). At any house , we face the same two choices as the brute force approach, but we make them using pre-calculated values:
- Rob house : We gain
nums[i]. Because of the adjacency constraint, we must add this to the maximum profit achieved ending at house (). - Skip house : We gain nothing from the current house. Our total profit remains the maximum profit achieved ending at house ().
The recurrence relation is:
Visual Description: Imagine a timeline or a 1D array filling up from left to right. To fill the box at index , you look strictly at the two boxes immediately to the left ( and ). You take the value from , add the current house's money, and compare it against the value in . The larger of the two becomes the value for box . Since we only ever look back two steps, we can discard older history, maintaining a constant memory footprint similar to calculating Fibonacci numbers iteratively.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 198: House Robber Solution & Explanation
Problem Overview
TL;DR: The optimal solution uses dynamic programming to iteratively determine the maximum profit at each house by choosing between maintaining the previous maximum or adding the current house's value to the maximum from two houses back.
The House Robber problem asks us to determine the maximum amount of money one can steal from a row of houses. The input is an integer array nums where each element represents the cash in a specific house. The critical constraint is that you cannot rob two adjacent houses. If you rob house , you cannot rob house or house . We must return the maximum possible sum satisfying this non-adjacency constraint.
This is a classic optimization problem found in LeetCode 198, widely used in technical interviews to test understanding of state-based decision making.
Brute Force Approach for House Robber
The brute force approach attempts to explore every possible valid combination of houses to rob. This is typically implemented using recursion. For every house at index , we make a binary decision:
- Rob the current house and proceed to solve the subproblem for house .
- Skip the current house and proceed to solve the subproblem for house .
The maximum of these two choices is the answer for the current step.
Pseudo-code:
function rob(index, nums):
if index < 0:
return 0
// Choice 1: Rob current house + max from index-2
robCurrent = nums[index] + rob(index - 2, nums)
// Choice 2: Skip current house, take max from index-1
skipCurrent = rob(index - 1, nums)
return max(robCurrent, skipCurrent)Time Complexity:
Each function call branches into two recursive calls. This creates a recursion tree with exponential growth.
Why it fails: The brute force solution fails due to overlapping subproblems. The function recalculates the optimal result for the same indices repeatedly. For an input size of , operations is astronomically high, leading to a Time Limit Exceeded (TLE) error on LeetCode.
Algorithm Strategy: Dynamic Programming (DP) Patterns
We will implement the Fibonacci Style DP approach. Instead of maintaining a full array dp of size , which would cost space, we can optimize space to by tracking only the two most recent states.
-
State Definition:
prev1: The maximum money robbable up to the previous house ().prev2: The maximum money robbable up to the house before the previous one ().
-
Base Cases:
- If the array is empty, return 0.
- If the array has one element, return that element.
-
Iterative Transition:
- Iterate through the array
nums. - Calculate
current_maxusing the recurrence: . - Update pointers:
prev2becomesprev1, andprev1becomescurrent_max.
- Iterate through the array
-
Result:
- After the loop,
prev1holds the maximum profit for the entire array.
- After the loop,
Execution Flow
Consider nums = [2, 7, 9, 3, 1].
-
Initialization:
- Set
prev2 = 0(imaginary house before index 0). - Set
prev1 = 0(imaginary house at index -1).
- Set
-
Iteration 1 (House 0, Value 2):
- Calculate: .
- Update:
prev2 = 0,prev1 = 2.
-
Iteration 2 (House 1, Value 7):
- Calculate: .
- Update:
prev2 = 2,prev1 = 7.
-
Iteration 3 (House 2, Value 9):
- Calculate: .
- Update:
prev2 = 7,prev1 = 11. - Logic: Robbing house 2 (9) + house 0 (2) is better than skipping house 2 (keeping 7).
-
Iteration 4 (House 3, Value 3):
- Calculate: .
- Update:
prev2 = 11,prev1 = 11. - Logic: Robbing house 3 (3) + house 1 (7) = 10. Skipping house 3 keeps 11. We skip.
-
Iteration 5 (House 4, Value 1):
- Calculate: .
- Update:
prev2 = 11,prev1 = 12.
-
Final Output: Return
prev1(12).
Proof of Correctness
The algorithm's correctness relies on the principle of optimality. For the final solution to be optimal, its sub-solutions must also be optimal.
Let be the maximum profit for the first houses. Base cases:
- (0 houses)
Inductive Step: Assume correctly represents the max profit for houses for all . For house , there are only two mutually exclusive possibilities regarding the optimal set:
- House is included. If so, house cannot be. The max profit is .
- House is excluded. The max profit is simply .
Since we take the maximum of these two exhaustive cases (), must be optimal.
Pattern Reuse Notes
The DP - 1D Array (Fibonacci Style) pattern used in LeetCode 198 is highly transferable. Understanding this recurrence relation helps solve several other popular interview questions:
- LeetCode 70: Climbing Stairs: Identical recurrence , but summing ways instead of maximizing value.
- LeetCode 91: Decode Ways: Similar "look back 1 or 2 steps" logic, but with conditional validity checks based on string characters.
- LeetCode 213: House Robber II: The same problem but with houses arranged in a circle. It is solved by running the House Robber I logic twice (once excluding the first house, once excluding the last).
- LeetCode 337: House Robber III: Applies the "rob or skip" logic to a Binary Tree structure, requiring a DFS approach rather than a linear loop.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 198
1class Solution {
2public:
3 int rob(vector<int>& nums) {
4 if (nums.empty()) return 0;
5
6 // prev1 represents dp[i-1], prev2 represents dp[i-2]
7 int prev1 = 0;
8 int prev2 = 0;
9
10 for (int num : nums) {
11 // Calculate max profit for current house
12 int temp = max(prev1, prev2 + num);
13
14 // Shift states forward
15 prev2 = prev1;
16 prev1 = temp;
17 }
18
19 return prev1;
20 }
21};Java Solution for LeetCode 198
1class Solution {
2 public int rob(int[] nums) {
3 if (nums == null || nums.length == 0) {
4 return 0;
5 }
6
7 // prev1 tracks the max money robbed up to the previous house
8 // prev2 tracks the max money robbed up to two houses ago
9 int prev1 = 0;
10 int prev2 = 0;
11
12 for (int num : nums) {
13 // Recurrence: max(skip current, rob current + prev2)
14 int currentMax = Math.max(prev1, prev2 + num);
15
16 // Update pointers for the next iteration
17 prev2 = prev1;
18 prev1 = currentMax;
19 }
20
21 return prev1;
22 }
23}Python Solution for LeetCode 198
1class Solution:
2 def rob(self, nums: List[int]) -> int:
3 # prev1 represents the max loot up to index i-1
4 # prev2 represents the max loot up to index i-2
5 prev1, prev2 = 0, 0
6
7 for num in nums:
8 # Calculate the new max including or excluding current num
9 # Update prev1 and prev2 simultaneously
10 temp = max(prev1, prev2 + num)
11 prev2 = prev1
12 prev1 = temp
13
14 return prev1