Facebook Pixel

1905. Count Sub Islands

Problem Description

You are given two m x n binary matrices grid1 and grid2 where each cell contains either 0 (representing water) or 1 (representing land).

An island is defined as a group of 1s that are connected horizontally or vertically (4-directionally). Any cells outside the grid boundaries are considered water.

The task is to find how many islands in grid2 are sub-islands of islands in grid1.

A sub-island is an island in grid2 where every single cell of that island has a corresponding land cell (1) at the same position in grid1. In other words, if you overlay an island from grid2 onto grid1, every land cell of the grid2 island must match with a land cell in grid1.

For example:

  • If grid2 has an island at positions [(0,0), (0,1), (1,0)]
  • This island is a sub-island only if grid1 also has 1s at all three positions: (0,0), (0,1), and (1,0)
  • If even one of these positions in grid1 is 0 (water), then the island in grid2 is NOT a sub-island

The function should return the total count of islands in grid2 that qualify as sub-islands.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem involves a 2D grid where land cells (1s) form islands through 4-directional connectivity. This is a classic graph problem where each cell is a node and adjacent land cells form edges.

Is it a tree?

  • No: The problem is not about tree structures. We have a 2D grid with potentially multiple disconnected islands, and cells can form cycles (e.g., a square of four 1s).

Is the problem related to directed acyclic graphs?

  • No: The connectivity between land cells is undirected (if cell A is connected to cell B, then B is also connected to A), and the problem doesn't involve directed edges or acyclic properties.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between nodes. Instead, we need to explore entire islands to verify if they are sub-islands.

Does the problem involve connectivity?

  • Yes: The core of the problem is about connectivity - we need to identify connected components (islands) in grid2 and check if all cells in each component have corresponding land cells in grid1.

Does the problem have small constraints?

  • Yes: With grid dimensions up to m x n, and the need to explore each island completely while checking conditions, the problem has manageable constraints suitable for DFS exploration.

DFS/backtracking?

  • Yes: DFS is perfect for exploring all cells of an island, marking visited cells, and simultaneously checking if the island qualifies as a sub-island.

Conclusion: The flowchart suggests using DFS to traverse each island in grid2, explore all its connected cells, and verify whether every cell in the island has a corresponding land cell in grid1.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to verify if every island in grid2 is completely "covered" by land in grid1. Think of it like placing a puzzle piece (island from grid2) on top of a board (grid1) - the piece only fits if there's solid ground underneath every part of it.

When we encounter a land cell (1) in grid2, we know it's part of an island. We need to explore the entire island to check two things:

  1. Find all cells that belong to this island in grid2
  2. Verify that each of these cells has a corresponding land cell in grid1

DFS is perfect for this because it naturally explores all connected cells of an island. As we traverse each cell of an island in grid2, we can simultaneously check if grid1 has a 1 at the same position. If we find even one cell where grid2 has land but grid1 has water, the entire island fails to be a sub-island.

To avoid counting the same island multiple times, we can mark visited cells in grid2 by setting them to 0 as we explore. This serves two purposes:

  • Prevents revisiting cells within the current island traversal
  • Ensures we don't count the same island again when we continue scanning the grid

The elegant part of the solution is combining the traversal with the validation - we use DFS to both explore the island and verify the sub-island condition in a single pass. The DFS returns 1 if all cells of the current island have corresponding land in grid1, and 0 otherwise. By starting a DFS from each unvisited land cell in grid2 and summing up the results, we get the total count of sub-islands.

Learn more about Depth-First Search, Breadth-First Search and Union Find patterns.

Solution Approach

The solution uses DFS to traverse each island in grid2 and validate whether it's a sub-island. Here's how the implementation works:

Main Function Structure:

  • We iterate through every cell (i, j) in grid2
  • When we find a land cell (grid2[i][j] == 1), we initiate a DFS from that position
  • Sum up the return values from all DFS calls to get the total count of sub-islands

DFS Implementation: The dfs(i, j) function performs the critical work:

  1. Check Current Cell: First, we check if grid1[i][j] is 1. We store this in variable ok. If it's 0, this island cannot be a sub-island, but we still need to explore all its cells.

  2. Mark as Visited: Set grid2[i][j] = 0 to mark this cell as visited, preventing infinite loops and ensuring we don't count the same island twice.

  3. Explore Neighbors: Using the direction array dirs = (-1, 0, 1, 0, -1) and pairwise, we generate the four adjacent positions:

    • (i-1, j) - up
    • (i, j+1) - right
    • (i+1, j) - down
    • (i, j-1) - left
  4. Recursive Exploration: For each valid neighbor that is:

    • Within grid boundaries: 0 <= x < m and 0 <= y < n
    • Is land in grid2: grid2[x][y] == 1

    We recursively call dfs(x, y). If any recursive call returns 0 (meaning that part of the island isn't covered by grid1), we set ok = 0.

  5. Return Result: The function returns ok, which will be:

    • 1 if this cell and all connected cells have corresponding land in grid1
    • 0 if any cell in this island doesn't have corresponding land in grid1

Key Design Decisions:

  • In-place Modification: We modify grid2 directly to mark visited cells, avoiding the need for a separate visited array.

  • Short-circuit Logic: Even if we discover the island isn't a sub-island (ok = 0), we continue exploring all its cells to properly mark them as visited.

  • Compact Direction Handling: The pairwise function with dirs array provides a clean way to iterate through the four directions.

The time complexity is O(m Γ— n) where we visit each cell at most once, and space complexity is O(m Γ— n) in the worst case for the recursion stack when the entire grid is one island.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach:

grid1 = [[1, 1, 0],     grid2 = [[1, 0, 0],
         [1, 1, 1],              [1, 1, 0],
         [0, 1, 0]]              [0, 1, 0]]

Step 1: Start scanning grid2 We begin at position (0,0). We find grid2[0][0] = 1, so we start a DFS.

Step 2: First DFS from (0,0)

  • Check grid1[0][0] = 1 βœ“ (ok = 1)
  • Mark grid2[0][0] = 0 (visited)
  • Check neighbors:
    • Up: out of bounds
    • Right: (0,1) has grid2[0][1] = 0 (water, skip)
    • Down: (1,0) has grid2[1][0] = 1 (land, recurse)
    • Left: out of bounds

Step 3: Recursive DFS from (1,0)

  • Check grid1[1][0] = 1 βœ“ (ok = 1)
  • Mark grid2[1][0] = 0 (visited)
  • Check neighbors:
    • Up: (0,0) already visited (now 0)
    • Right: (1,1) has grid2[1][1] = 1 (land, recurse)
    • Down: (2,0) has grid2[2][0] = 0 (water, skip)
    • Left: out of bounds

Step 4: Recursive DFS from (1,1)

  • Check grid1[1][1] = 1 βœ“ (ok = 1)
  • Mark grid2[1][1] = 0 (visited)
  • Check neighbors:
    • Up: (0,1) has grid2[0][1] = 0 (water, skip)
    • Right: (1,2) has grid2[1][2] = 0 (water, skip)
    • Down: (2,1) has grid2[2][1] = 1 (land, recurse)
    • Left: (1,0) already visited (now 0)

Step 5: Recursive DFS from (2,1)

  • Check grid1[2][1] = 1 βœ“ (ok = 1)
  • Mark grid2[2][1] = 0 (visited)
  • Check neighbors: all are water or out of bounds
  • Returns 1 to step 4

Step 6: Backtrack and complete

  • Step 4 returns 1 to step 3
  • Step 3 returns 1 to step 2
  • Step 2 returns 1 (this island is a sub-island!)
  • Count = 1

Step 7: Continue scanning grid2 The rest of grid2 is now all 0s (water or visited), so no more islands to check.

Final Result: 1 sub-island

The island at positions [(0,0), (1,0), (1,1), (2,1)] in grid2 has corresponding land cells at all positions in grid1, making it a valid sub-island.

Solution Implementation

1class Solution:
2    def countSubIslands(self, grid1: List[List[int]], grid2: List[List[int]]) -> int:
3        def dfs(row: int, col: int) -> int:
4            """
5            Perform DFS to check if current island in grid2 is a sub-island of grid1.
6          
7            Args:
8                row: Current row index
9                col: Current column index
10              
11            Returns:
12                1 if the current cell and all connected cells form a sub-island, 0 otherwise
13            """
14            # Check if current cell in grid2 corresponds to land in grid1
15            is_sub_island = grid1[row][col]
16          
17            # Mark current cell as visited by setting it to 0
18            grid2[row][col] = 0
19          
20            # Explore all 4 adjacent directions (up, right, down, left)
21            for i in range(4):
22                next_row = row + directions[i]
23                next_col = col + directions[i + 1]
24              
25                # Check if next cell is within bounds and is unvisited land in grid2
26                if (0 <= next_row < rows and 
27                    0 <= next_col < cols and 
28                    grid2[next_row][next_col] == 1):
29                  
30                    # Recursively check the connected component
31                    # If any part is not a sub-island, mark entire island as invalid
32                    if not dfs(next_row, next_col):
33                        is_sub_island = 0
34          
35            return is_sub_island
36      
37        # Get dimensions of the grids
38        rows = len(grid1)
39        cols = len(grid1[0])
40      
41        # Direction vectors for exploring 4 adjacent cells (up, right, down, left)
42        # Using a sliding window approach: (directions[i], directions[i+1]) gives direction pairs
43        directions = [-1, 0, 1, 0, -1]
44      
45        # Count sub-islands by performing DFS from each unvisited land cell in grid2
46        sub_island_count = 0
47        for row in range(rows):
48            for col in range(cols):
49                if grid2[row][col] == 1:
50                    sub_island_count += dfs(row, col)
51      
52        return sub_island_count
53
1class Solution {
2    // Direction vectors for traversing up, right, down, left
3    private final int[] DIRECTIONS = {-1, 0, 1, 0, -1};
4    private int[][] grid1;
5    private int[][] grid2;
6    private int rows;
7    private int cols;
8
9    /**
10     * Counts the number of sub-islands in grid2 that are also present in grid1.
11     * A sub-island is an island in grid2 where all its land cells correspond to land cells in grid1.
12     * 
13     * @param grid1 The first binary matrix representing islands
14     * @param grid2 The second binary matrix representing islands
15     * @return The count of sub-islands
16     */
17    public int countSubIslands(int[][] grid1, int[][] grid2) {
18        // Initialize dimensions and grid references
19        rows = grid1.length;
20        cols = grid1[0].length;
21        this.grid1 = grid1;
22        this.grid2 = grid2;
23      
24        int subIslandCount = 0;
25      
26        // Traverse through all cells in grid2
27        for (int i = 0; i < rows; i++) {
28            for (int j = 0; j < cols; j++) {
29                // If we find an unvisited land cell in grid2, explore the island
30                if (grid2[i][j] == 1) {
31                    // DFS returns 1 if this island is a sub-island, 0 otherwise
32                    subIslandCount += dfs(i, j);
33                }
34            }
35        }
36      
37        return subIslandCount;
38    }
39
40    /**
41     * Performs depth-first search to explore an island in grid2 and determine if it's a sub-island.
42     * Marks visited cells by setting them to 0 in grid2.
43     * 
44     * @param row Current row index
45     * @param col Current column index
46     * @return 1 if the island is a valid sub-island, 0 otherwise
47     */
48    private int dfs(int row, int col) {
49        // Check if current cell in grid1 is land (1) or water (0)
50        // This determines if current position is valid for a sub-island
51        int isValidSubIsland = grid1[row][col];
52      
53        // Mark current cell as visited in grid2
54        grid2[row][col] = 0;
55      
56        // Explore all 4 adjacent directions
57        for (int k = 0; k < 4; k++) {
58            int nextRow = row + DIRECTIONS[k];
59            int nextCol = col + DIRECTIONS[k + 1];
60          
61            // Check if the next cell is within bounds and is unvisited land in grid2
62            if (nextRow >= 0 && nextRow < rows && 
63                nextCol >= 0 && nextCol < cols && 
64                grid2[nextRow][nextCol] == 1) {
65              
66                // Recursively check the neighboring cell
67                // Use bitwise AND to maintain sub-island validity
68                // If any part of the island is not on grid1 land, the entire island is invalid
69                isValidSubIsland &= dfs(nextRow, nextCol);
70            }
71        }
72      
73        return isValidSubIsland;
74    }
75}
76
1class Solution {
2public:
3    int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
4        int rows = grid1.size();
5        int cols = grid1[0].size();
6        int subIslandCount = 0;
7      
8        // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
9        int directions[5] = {-1, 0, 1, 0, -1};
10      
11        // DFS function to explore an island in grid2 and check if it's a sub-island
12        // Returns 1 if all cells of the current island in grid2 are also 1 in grid1
13        // Returns 0 otherwise
14        function<int(int, int)> dfs = [&](int row, int col) -> int {
15            // Check if current cell in grid2 corresponds to land (1) in grid1
16            int isSubIsland = grid1[row][col];
17          
18            // Mark current cell as visited by setting it to 0
19            grid2[row][col] = 0;
20          
21            // Explore all 4 adjacent cells
22            for (int k = 0; k < 4; ++k) {
23                int nextRow = row + directions[k];
24                int nextCol = col + directions[k + 1];
25              
26                // Check boundaries and if the adjacent cell is unvisited land in grid2
27                if (nextRow >= 0 && nextRow < rows && 
28                    nextCol >= 0 && nextCol < cols && 
29                    grid2[nextRow][nextCol] == 1) {
30                    // Recursively check the adjacent cell and update sub-island status
31                    // Using bitwise AND to ensure all cells must be 1 in grid1
32                    isSubIsland &= dfs(nextRow, nextCol);
33                }
34            }
35          
36            return isSubIsland;
37        };
38      
39        // Iterate through all cells in grid2
40        for (int i = 0; i < rows; ++i) {
41            for (int j = 0; j < cols; ++j) {
42                // If we find an unvisited land cell in grid2, start DFS
43                if (grid2[i][j] == 1) {
44                    // Add to count if the entire island is a sub-island
45                    subIslandCount += dfs(i, j);
46                }
47            }
48        }
49      
50        return subIslandCount;
51    }
52};
53
1/**
2 * Counts the number of sub-islands in grid2 that are also present in grid1.
3 * A sub-island in grid2 is an island where all its land cells also correspond to land cells in grid1.
4 * 
5 * @param grid1 - The first binary grid where 1 represents land and 0 represents water
6 * @param grid2 - The second binary grid where we need to find sub-islands
7 * @returns The number of sub-islands in grid2
8 */
9function countSubIslands(grid1: number[][], grid2: number[][]): number {
10    // Get grid dimensions
11    const rows: number = grid1.length;
12    const cols: number = grid1[0].length;
13  
14    // Counter for sub-islands
15    let subIslandCount: number = 0;
16  
17    // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
18    const directions: number[] = [-1, 0, 1, 0, -1];
19  
20    /**
21     * Performs depth-first search to explore an island in grid2
22     * and checks if it's a sub-island of grid1.
23     * 
24     * @param row - Current row position
25     * @param col - Current column position
26     * @returns 1 if the current island is a sub-island, 0 otherwise
27     */
28    const depthFirstSearch = (row: number, col: number): number => {
29        // Check if current cell in grid2 corresponds to land in grid1
30        // Using bitwise AND to maintain the "all cells must match" condition
31        let isSubIsland: number = grid1[row][col];
32      
33        // Mark current cell as visited by setting it to 0
34        grid2[row][col] = 0;
35      
36        // Explore all 4 adjacent directions
37        for (let k = 0; k < 4; k++) {
38            const nextRow: number = row + directions[k];
39            const nextCol: number = col + directions[k + 1];
40          
41            // Check if the next position is valid and contains unvisited land
42            if (nextRow >= 0 && nextRow < rows && 
43                nextCol >= 0 && nextCol < cols && 
44                grid2[nextRow][nextCol] === 1) {
45                // Recursively explore and combine results using bitwise AND
46                // This ensures isSubIsland becomes 0 if any cell doesn't match
47                isSubIsland &= depthFirstSearch(nextRow, nextCol);
48            }
49        }
50      
51        return isSubIsland;
52    };
53  
54    // Iterate through all cells in grid2
55    for (let i = 0; i < rows; i++) {
56        for (let j = 0; j < cols; j++) {
57            // If we find unvisited land in grid2, explore the entire island
58            if (grid2[i][j] === 1) {
59                // Add to count if the island is a sub-island
60                subIslandCount += depthFirstSearch(i, j);
61            }
62        }
63    }
64  
65    return subIslandCount;
66}
67

Time and Space Complexity

Time Complexity: O(m Γ— n) where m is the number of rows and n is the number of columns in the grids.

The algorithm visits each cell in grid2 at most once. The main loop iterates through all m Γ— n cells, and for each cell that contains a 1 in grid2, it triggers a DFS traversal. During the DFS, each cell is visited exactly once because once visited, it's marked as 0 (preventing revisits). Therefore, the total number of operations across all DFS calls is bounded by the total number of cells, resulting in O(m Γ— n) time complexity.

Space Complexity: O(m Γ— n) where m is the number of rows and n is the number of columns in the grids.

The space complexity is dominated by the recursive call stack during DFS traversal. In the worst case, if grid2 contains a single island that spans all cells in a snake-like pattern, the DFS recursion depth could reach m Γ— n levels deep. This would occur when the island forms a path that visits every cell sequentially, causing the recursive calls to stack up to a maximum depth of m Γ— n. Therefore, the space complexity is O(m Γ— n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall: Early Termination When Finding Non-Matching Cell

The Problem: A common mistake is to immediately return 0 when encountering a cell in the island that doesn't have corresponding land in grid1, like this:

def dfs(row: int, col: int) -> int:
    # WRONG: Early return when finding water in grid1
    if grid1[row][col] == 0:
        return 0  # This stops exploring the rest of the island!
  
    grid2[row][col] = 0
  
    for i in range(4):
        next_row = row + directions[i]
        next_col = col + directions[i + 1]
        if (0 <= next_row < rows and 
            0 <= next_col < cols and 
            grid2[next_row][next_col] == 1):
            if not dfs(next_row, next_col):
                return 0
  
    return 1

Why This Fails: When you return early, you leave parts of the island in grid2 unexplored and unmarked. These unvisited cells remain as 1s and will be encountered again in the main loop, causing them to be incorrectly counted as separate islands.

Example Scenario:

grid1:          grid2:
1 0 0           1 1 0
1 1 0           1 1 0

With early termination:

  1. Start DFS at (0,0) - it's valid
  2. Move to (0,1) - grid1[0][1] is 0, so return 0 immediately
  3. The cells (1,0) and (1,1) in grid2 remain unvisited (still 1s)
  4. Main loop later starts new DFS from (1,0), incorrectly counting it as another island

The Correct Approach: Always explore the entire island to mark all cells as visited, but track whether it's a valid sub-island:

def dfs(row: int, col: int) -> int:
    # Store validation result but continue exploration
    is_sub_island = grid1[row][col]
  
    # Mark as visited
    grid2[row][col] = 0
  
    # Explore ALL connected cells regardless of validity
    for i in range(4):
        next_row = row + directions[i]
        next_col = col + directions[i + 1]
        if (0 <= next_row < rows and 
            0 <= next_col < cols and 
            grid2[next_row][next_col] == 1):
            # Update validity but continue exploring
            if not dfs(next_row, next_col):
                is_sub_island = 0
  
    return is_sub_island

This ensures:

  • Every cell of each island is visited exactly once
  • Islands are correctly identified as single connected components
  • The sub-island validation is properly propagated through the entire island
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More