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 1
s 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 has1
s at all three positions:(0,0)
,(0,1)
, and(1,0)
- If even one of these positions in
grid1
is0
(water), then the island ingrid2
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 (
1
s) 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
1
s).
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 ingrid1
.
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
.
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:
- Find all cells that belong to this island in
grid2
- 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)
ingrid2
- 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:
-
Check Current Cell: First, we check if
grid1[i][j]
is1
. We store this in variableok
. If it's0
, this island cannot be a sub-island, but we still need to explore all its cells. -
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. -
Explore Neighbors: Using the direction array
dirs = (-1, 0, 1, 0, -1)
andpairwise
, we generate the four adjacent positions:(i-1, j)
- up(i, j+1)
- right(i+1, j)
- down(i, j-1)
- left
-
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 returns0
(meaning that part of the island isn't covered bygrid1
), we setok = 0
. - Within grid boundaries:
-
Return Result: The function returns
ok
, which will be:1
if this cell and all connected cells have corresponding land ingrid1
0
if any cell in this island doesn't have corresponding land ingrid1
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 withdirs
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 EvaluatorExample 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)
- Up: (0,1) has
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 1
s 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:
- Start DFS at
(0,0)
- it's valid - Move to
(0,1)
- grid1[0][1] is 0, so return 0 immediately - The cells
(1,0)
and(1,1)
in grid2 remain unvisited (still 1s) - 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
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro Once we have a strong grasp of recursion and Depth First Search we can now introduce Disjoint Set Union DSU This data structure is motivated by the following problem Suppose we have sets of elements
Want a Structured Path to Master System Design Too? Donβt Miss This!