Lessons
Arrays
Dynamic Programming
Graph
Hashing
House Robber Problem
Imagine you’re a thief trying to rob houses along a street. However, there’s a constraint: robbing two neighbouring houses will alert the police! This setup defines the classic House Robber problem. Solving it requires smart optimization, and in this guide, we'll break it down step-by-step using dynamic programming, memoization, and effective state transitions.
Let’s dive into how you can maximize your loot, one smart move at a time!
What is the House Robber Problem?
The House Robber problem challenges you to select houses from a house sequence in a way that maximizes your total loot or max profit, without ever robbing two neighbouring houses.
Problem Statement:
Given an array where each element represents the amount of money in a house, determine the maximum amount you can rob without triggering the security systems by robbing adjacent houses.
Why Dynamic Programming is the Key
This problem is a textbook case for dynamic programming. Every decision (to rob a house or skip it) depends on previous decisions. Using a naive recursive solution would lead to a lot of repeated calculations, making it inefficient. That’s where memoization and state transition ideas make a huge difference!
Approaches to Solve the House Robber Problem
1. Recursive Solution
Let's first try a basic recursive solution. You either rob the current house and skip the next, or skip the current house entirely.
Code Example: Recursive Solution
python
1 2 3 4 5 6 7 8
def rob(houses, index=0): if index >= len(houses): return 0 # Choose to rob or skip return max( houses[index] + rob(houses, index + 2), rob(houses, index + 1) )
Analysis:
- Time Complexity: O(2^n) — Exponential because of repeated subproblems.
- Space Complexity: O(n) due to recursion stack.
Drawback:
The recursive solution recalculates the same subproblems many times, leading to inefficiency. We need optimization.
2. Dynamic Programming with Memoization
By storing results of already-solved subproblems (memoization), we can greatly improve the performance.
Code Example: DP with Memoization
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
def rob(houses): memo = {} def dp(index): if index >= len(houses): return 0 if index in memo: return memo[index] result = max( houses[index] + dp(index + 2), dp(index + 1) ) memo[index] = result return result return dp(0)
Key Concepts:
- Use a dictionary
memo
to save the max profit at each step. - Avoid recomputing states already evaluated.
Complexity:
- Time Complexity: O(n)
- Space Complexity: O(n)
3. Bottom-Up Dynamic Programming
Another way to apply dynamic programming is using a bottom-up approach, calculating the result iteratively without recursion.
Code Example: Bottom-Up Approach
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14
def rob(houses): if not houses: return 0 if len(houses) == 1: return houses[0] dp = [0] * len(houses) dp[0] = houses[0] dp[1] = max(houses[0], houses[1]) for i in range(2, len(houses)): dp[i] = max(houses[i] + dp[i-2], dp[i-1]) return dp[-1]
How It Works:
- Build up the solution using prior results.
- Perform a state transition:
- Either take the current house + profit two houses back
- Or skip the current house and take the previous profit.
Complexity:
- Time Complexity: O(n)
- Space Complexity: O(n)
4. Space-Optimized Dynamic Programming
We can further improve by reducing the space complexity to O(1). We only need the last two results at any point.
Code Example: Space Optimization
python
1 2 3 4 5 6 7 8 9 10
def rob(houses): prev1 = 0 prev2 = 0 for money in houses: temp = prev1 prev1 = max(money + prev2, prev1) prev2 = temp return prev1
Highlights:
- Track only two variables instead of a full array.
- Best choice for larger datasets.
Understanding State Transition
The key state transition formula is:
python
1
dp[i] = max(houses[i] + dp[i-2], dp[i-1])
This simply means at each house, you either:
- Rob it, and add the profit two houses back (since neighbouring is forbidden), or
- Skip it and take the profit till the previous house.
Common Constraints in the House Robber Problem
When solving the House Robber problem, remember:
- No robbing of two neighbouring houses.
- All values are non-negative.
- Input list might be empty.
These constraints drive the need for careful optimization.
Real-World Relevance
Although hypothetical, this problem teaches important problem-solving techniques:
- Handling adjacent dependency problems.
- Recognizing opportunities for dynamic programming.
- Effective use of memoization for repeated subproblems.
- Reducing algorithm complexity for real-time applications.
Conclusion
The House Robber Problem is a fantastic way to practice and master dynamic programming, optimization, and efficient state transitions. Starting with a simple recursive solution, improving it through memoization, and finally optimizing with dynamic programming are critical skills for every aspiring problem solver. Whether you choose recursion or the space-optimized approach, understanding these techniques will equip you to tackle many more challenging algorithmic problems!