Number of Distinct Islands

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.
Below is a step-by-step Dynamic Programming breakdown for the classic House Robber problem (LeetCode #198). We’ll follow your six-step approach:
1️⃣ Problem Explanation (in Simple Words)
You are given a list (array) of non-negative integers where each integer represents the amount of money stashed in a house along a street. You cannot rob two adjacent houses (to avoid triggering alarms). Your goal is to maximize the total amount of money robbed.
Input: An array of integers
nums, e.g.,[2, 7, 9, 3, 1].Output: A single integer (the maximum amount of money you can rob without ever robbing two adjacent houses).
Example
Example: nums = [2, 7, 9, 3, 1]
If you rob house 1 (
2$), you cannot rob house 2 (7$), but you can consider house 3, and so on.One optimal solution is to rob houses with
7$+9$+1$=17$.No two of these robbed houses are next to each other.
2️⃣ Draw the Decision Tree (to Visualize the Recursion)
To understand the recursive calls, let’s label the houses by index: 0, 1, 2, 3, 4.
We define a function rob(i) = maximum amount of money you can rob from houses in the range [i .. end].
At each house i, you have two choices:
Rob house i: then you skip house i+1 and continue from house i+2.
Don’t rob house i: then you move on to house i+1.
Decision tree for nums = [2, 7, 9, 3, 1]:
rob(0)
/ \
Rob house 0 (2) Skip house 0 (0)
/ \
rob(2) rob(1)
/ \ / \
rob(2)+rob(4) ... rob(1)+rob(3) ...
(and so on...)
If we rob house 0 (amount = 2) → our next decision is at
rob(2).If we skip house 0 → we continue with
rob(1).
Eventually, the recursion explores all valid ways to rob houses without adjacency conflict. We pick the maximum sum among all these paths.
3️⃣ Recursion + Memoization (Top-Down Approach)
Key Idea
Recursive Function: Let
robRec(i)be the maximum amount we can rob from houses starting at indexi.Choices:
rob house i → gain
nums[i]+ move toi+2(because housei+1is off-limits if we rob housei).skip house i → just move to
i+1.
Base Cases:
- If
i >= nums.length, no houses left → return0.
- If
Memoization: We store results in a cache (e.g.,
memo[i]) to avoid repeated calculations.
JavaScript Code (Top-Down DP)
/**
* Rob houses using recursion + memoization.
* @param {number[]} nums
* @return {number} Max amount of money that can be robbed.
*/
function rob(nums) {
const n = nums.length;
const memo = new Array(n).fill(-1);
function robRec(i) {
// Base case: no houses left to consider
if (i >= n) return 0;
// If already computed, return from memo
if (memo[i] !== -1) {
return memo[i];
}
// Option 1: Rob this house
const robCurrent = nums[i] + robRec(i + 2);
// Option 2: Skip this house
const skipCurrent = robRec(i + 1);
// Take the max
memo[i] = Math.max(robCurrent, skipCurrent);
return memo[i];
}
// Compute starting from house 0
return robRec(0);
}
// Test
console.log(rob([2, 7, 9, 3, 1])); // 12 -> Explanation: 7 + 3 + 2 or 2 + 9 + 1, etc. Actually the best is 2+9+1=12 or 7+3+? Let's see
// House 0: 2, skip 1, then rob 2: 9 => total 11, skip 3 or rob 3? Let's see carefully:
// Actually let's do a quick check: The best combination is 2 + 9 + 1 = 12, or 7 + 3 = 10, or 7 + 3 + 1 is not possible because 3 and 1 are adjacent to 7 if we skip only 2? Wait, we skip house 2 if we rob house 1.
// So the maximum is indeed 12.
Note on example: Depending on the array, the maximum can vary. For
[2, 7, 9, 3, 1], the best is2 + 9 + 1 = 12. Another approach might yield the same or different sum, but 12 is the maximum.
4️⃣ Tabulation (Bottom-Up Approach)
Instead of top-down recursion, we can fill a dp array iteratively:
dp[i] = the maximum amount of money that can be robbed from house
ionward.Transition:
dp[i]=max(nums[i]+dp[i+2],dp[i+1])
rob house i + whatever you get from
i+2skip house i and look at
dp[i+1]
We want dp[0] in the end.
We fill dp from the right to left:
- Because
dp[i]depends ondp[i+1]anddp[i+2].
- Because
Bottom-Up DP JavaScript
/**
* Rob houses using bottom-up tabulation.
* @param {number[]} nums
* @return {number} Max amount of money that can be robbed.
*/
function robTab(nums) {
const n = nums.length;
if (n === 0) return 0;
// dp[i] = max amount that can be robbed from i..end
const dp = new Array(n + 2).fill(0);
// We add 2 extra slots to safely handle dp[i+2] without out-of-bounds
// Fill from right to left
for (let i = n - 1; i >= 0; i--) {
// Rob i OR skip i
dp[i] = Math.max(nums[i] + dp[i + 2], dp[i + 1]);
}
// The result is the max amount from house 0
return dp[0];
}
// Test
console.log(robTab([2, 7, 9, 3, 1])); // 12
5️⃣ Optimize Space Complexity (If Possible)
In the tabulation solution, note that to compute dp[i], we only need dp[i+1] and dp[i+2]. So we don’t need the entire dparray.
Space-Optimized Bottom-Up Approach
We can use just two variables to store dp[i+1] and dp[i+2] while iterating:
/**
* Space-optimized version of robTab.
*/
function robTabOptimized(nums) {
let robNext = 0; // dp[i+1]
let robNextNext = 0; // dp[i+2]
// Iterate from right to left
for (let i = nums.length - 1; i >= 0; i--) {
const current = Math.max(nums[i] + robNextNext, robNext);
// Shift
robNextNext = robNext;
robNext = current;
}
return robNext; // This is dp[0] at the end
}
// Test
console.log(robTabOptimized([2, 7, 9, 3, 1])); // 12
Here:
robNextrepresentsdp[i+1](the maximum loot starting from housei+1).robNextNextrepresentsdp[i+2].Each iteration calculates
dp[i], which we store incurrent. Then we shift the values accordingly.
This reduces our space usage from O(n) to O(1), ignoring the input array.
6️⃣ Time Complexity Analysis
Recursion + Memoization
We have
nhouses, and we definerobRec(i)for eachi.Number of states:
n.Each state does O(1) work to combine results from
robRec(i+1)orrobRec(i+2).So the DP approach is O(n) in time once memoized.
Tabulation (Bottom-Up)
We fill
dparray fromn-1down to0, and each step is O(1).Overall O(n) time.
Space Optimization
Tabulation can be done in O(1) space (beyond the input) using the two-variable method.
The time complexity remains O(n).
Summary
The House Robber problem is a classic demonstration of DP where we decide for each house: rob it (and skip the next one) or skip it (and proceed to the next). We saw:
Recursive Explanation + Decision Tree
Top-Down Memoized Code
Bottom-Up Tabulation Code
Space Optimization (down to constant space)
Time Complexity of O(n) in all approaches.
With these clear steps, you’ll easily handle variations like House Robber II (circular street) and Delete and Earn(another spin on the same logic).
Congrats on another DP problem tackled! Keep up the great work.




