Skip to main content

Command Palette

Search for a command to run...

Number of Distinct Islands

Published
7 min readView as Markdown
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:

  1. Rob house i: then you skip house i+1 and continue from house i+2.

  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

  1. Recursive Function: Let robRec(i) be the maximum amount we can rob from houses starting at index i.

  2. Choices:

    • rob house i → gain nums[i] + move to i+2 (because house i+1 is off-limits if we rob house i).

    • skip house i → just move to i+1.

  3. Base Cases:

    • If i >= nums.length, no houses left → return 0.
  4. 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 is 2 + 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:

  1. dp[i] = the maximum amount of money that can be robbed from house i onward.

  2. Transition:

    dp[i]=max⁡(nums[i]+dp[i+2],dp[i+1])

    • rob house i + whatever you get from i+2

    • skip house i and look at dp[i+1]

  3. We want dp[0] in the end.

  4. We fill dp from the right to left:

    • Because dp[i] depends on dp[i+1] and dp[i+2].

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:

  • robNext represents dp[i+1] (the maximum loot starting from house i+1).

  • robNextNext represents dp[i+2].

  • Each iteration calculates dp[i], which we store in current. 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

  1. Recursion + Memoization

    • We have n houses, and we define robRec(i) for each i.

    • Number of states: n.

    • Each state does O(1) work to combine results from robRec(i+1) or robRec(i+2).

    • So the DP approach is O(n) in time once memoized.

  2. Tabulation (Bottom-Up)

    • We fill dp array from n-1 down to 0, and each step is O(1).

    • Overall O(n) time.

  3. 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:

  1. Recursive Explanation + Decision Tree

  2. Top-Down Memoized Code

  3. Bottom-Up Tabulation Code

  4. Space Optimization (down to constant space)

  5. 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.

More from this blog

Dynamic Programming

28 posts