711. Number of Distinct Islands II π
Problem Description
You have an m x n
binary matrix grid
where 1
represents land and 0
represents water. An island is formed by connecting adjacent lands horizontally or vertically (4-directionally connected). The entire grid is surrounded by water.
The key challenge is identifying distinct islands. Two islands are considered the same if:
- They have the exact same shape, OR
- One can be transformed into the other through:
- Rotation: 90Β°, 180Β°, or 270Β° rotations
- Reflection: flipping horizontally (left/right) or vertically (up/down)
Your task is to count how many distinct island shapes exist in the grid.
For example, if you have an L-shaped island and another island that looks like a rotated L, they count as the same shape (only 1 distinct island). Similarly, if one island is a mirror image of another, they're also considered the same.
The solution uses DFS to find all cells belonging to each island, then normalizes each island's shape by generating all 8 possible transformations (4 rotations Γ 2 reflections). By comparing the canonical form of each island shape, duplicate shapes are eliminated using a set, giving us the count of truly distinct 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 we need to explore connected components (islands). Each cell can be viewed as a node, and adjacent cells (4-directionally) form edges. Islands are essentially connected components in this implicit graph.
Is it a tree?
- No: The grid structure is not a tree. It's a general graph where we have multiple disconnected components (islands), and the structure doesn't follow parent-child relationships like a tree.
Is the problem related to directed acyclic graphs?
- No: The problem doesn't involve directed edges or acyclic properties. We're dealing with an undirected graph where connections between land cells are bidirectional.
Is the problem related to shortest paths?
- No: We're not finding shortest paths between points. Instead, we need to identify and explore all cells belonging to each island to determine their shapes.
Does the problem involve connectivity?
- Yes: The core of the problem is about connectivity - we need to find all connected components (islands) of
1
s in the grid and identify their distinct shapes.
Does the problem have small constraints?
- Yes: While not explicitly stated, grid problems typically have manageable constraints that allow for exploration algorithms. We need to explore each island completely to capture its shape.
DFS/backtracking?
- Yes: DFS is perfect for exploring each island completely. Starting from any land cell, we recursively visit all connected land cells to capture the complete shape of the island.
Conclusion: The flowchart suggests using DFS (Depth-First Search) to explore connected components in the grid. DFS allows us to traverse each island completely, recording all cell positions to determine the island's shape for comparison with other islands after normalization.
Intuition
When we look at this problem, the first challenge is understanding what makes two islands "the same." If we only cared about exact matches, we could simply record each island's shape and compare them directly. But since islands can be rotated or reflected and still be considered identical, we need a way to handle all these transformations.
Think about it like puzzle pieces - if you have two L-shaped pieces, one might be rotated 90Β° from the other, but they're still the same piece. The key insight is that for any given shape, there are exactly 8 possible orientations when considering all rotations and reflections:
- 4 rotations (0Β°, 90Β°, 180Β°, 270Β°)
- Each rotation can also be reflected (flipped)
So our strategy becomes: for each island we find, generate all 8 possible transformations of its shape. But we still need a consistent way to compare shapes. This is where normalization comes in.
The normalization process works by:
- Taking all 8 transformations of an island's coordinates
- Sorting each transformation to create a consistent ordering
- Translating each shape so its "first" point is at origin
(0, 0)
- this makes the shape position-independent - Choosing the lexicographically smallest transformation as the "canonical" representation
By using DFS to explore each island and collect its coordinates, then normalizing these coordinates to a canonical form, we can use a set to automatically handle duplicates. Two islands that are the same shape (considering rotations and reflections) will have the same canonical form and thus be counted only once.
The beauty of this approach is that we don't need to explicitly check if two islands match through various transformations - the normalization process handles all of that automatically by reducing every possible variant of a shape to a single, unique representation.
Learn more about Depth-First Search, Breadth-First Search and Union Find patterns.
Solution Approach
The implementation uses DFS combined with a normalization technique to identify distinct islands. Let's walk through the key components:
1. DFS Exploration (dfs
function)
The DFS function explores an entire island starting from a given cell (i, j)
:
- Records the current cell's coordinates in the
shape
list - Marks the cell as visited by setting
grid[i][j] = 0
- Recursively explores all 4 adjacent cells (up, down, left, right)
- Builds a complete list of coordinates representing the island's shape
def dfs(i, j, shape):
shape.append([i, j])
grid[i][j] = 0
for a, b in [[1, 0], [-1, 0], [0, 1], [0, -1]]:
x, y = i + a, j + b
if 0 <= x < m and 0 <= y < n and grid[x][y] == 1:
dfs(x, y, shape)
2. Shape Normalization (normalize
function)
This is the core logic that handles rotations and reflections. For each island shape, it generates all 8 possible transformations:
shapes[0].append([i, j]) # Original shapes[1].append([i, -j]) # Reflect horizontally shapes[2].append([-i, j]) # Reflect vertically shapes[3].append([-i, -j]) # Rotate 180Β° shapes[4].append([j, i]) # Rotate 90Β° counter-clockwise shapes[5].append([j, -i]) # Rotate 90Β° + reflect shapes[6].append([-j, i]) # Rotate 270Β° + reflect shapes[7].append([-j, -i]) # Rotate 270Β°
After generating all transformations:
- Each transformation is sorted to create consistent ordering
- Each shape is translated so its first point becomes
(0, 0)
:for i in range(len(e) - 1, -1, -1): e[i][0] -= e[0][0] e[i][1] -= e[0][1]
- The lexicographically smallest transformation is chosen as the canonical form
- The result is converted to a tuple (immutable and hashable) for set storage
3. Main Algorithm
The main loop:
- Iterates through every cell in the grid
- When a land cell (
1
) is found, initiates DFS to explore the entire island - Normalizes the island's shape to get its canonical representation
- Adds the canonical form to a set (automatically handles duplicates)
- Returns the size of the set, which gives the count of distinct islands
s = set()
for i in range(m):
for j in range(n):
if grid[i][j]:
shape = []
dfs(i, j, shape)
s.add(normalize(shape))
return len(s)
The time complexity is O(m Γ n Γ k)
where k
is the average size of an island, as we need to normalize each island's shape. The space complexity is O(m Γ n)
for storing island shapes and the set of canonical forms.
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 how the solution identifies distinct islands:
Grid: 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 1
Step 1: Find First Island Starting at (0,0), we encounter a '1'. DFS explores the island:
- Visit (0,0), add to shape: [(0,0)]
- Visit (0,1), add to shape: [(0,0), (0,1)]
- Visit (1,0), add to shape: [(0,0), (0,1), (1,0)]
- Mark all visited cells as 0
Island 1 coordinates: [(0,0), (0,1), (1,0)] - This is an L-shape.
Step 2: Normalize First Island Generate all 8 transformations of [(0,0), (0,1), (1,0)]:
- Original: [(0,0), (0,1), (1,0)]
- Reflect H: [(0,0), (0,-1), (1,0)]
- Reflect V: [(0,0), (0,1), (-1,0)]
- Rotate 180Β°: [(0,0), (0,-1), (-1,0)]
- Rotate 90Β° CCW: [(0,0), (1,0), (0,1)]
- Rotate 90Β° + reflect: [(0,0), (-1,0), (0,1)]
- Rotate 270Β° + reflect: [(0,0), (1,0), (0,-1)]
- Rotate 270Β°: [(0,0), (-1,0), (0,-1)]
After sorting each transformation and translating to origin:
- Transform 1 sorted: [(0,0), (0,1), (1,0)] β After translation: [(0,0), (0,1), (1,0)]
- Transform 5 sorted: [(0,0), (0,1), (1,0)] β After translation: [(0,0), (0,1), (1,0)]
- (Other transforms produce different results)
Choose lexicographically smallest: ((0,0), (0,1), (1,0))
Add to set: {((0,0), (0,1), (1,0))}
Step 3: Find Second Island Starting at (2,3), we find another island:
- Visit (2,3), add to shape: [(2,3)]
- Visit (2,4), add to shape: [(2,3), (2,4)]
- Visit (3,4), add to shape: [(2,3), (2,4), (3,4)]
Island 2 coordinates: [(2,3), (2,4), (3,4)] - This is also an L-shape, but rotated!
Step 4: Normalize Second Island Original coordinates: [(2,3), (2,4), (3,4)]
First, let's look at what happens with just the relative positions (ignoring the actual positions):
- Relative to first point: [(0,0), (0,1), (1,1)]
Generate transformations:
- Original: [(0,0), (0,1), (1,1)]
- Rotate 90Β°: [(0,0), (1,0), (1,-1)]
- Rotate 180Β°: [(0,0), (0,-1), (-1,-1)]
- Rotate 270Β°: [(0,0), (-1,0), (-1,1)] ... and reflections
After normalization, one of the transformations (specifically a 270Β° rotation) produces:
- Sorted and translated: [(0,0), (0,1), (1,0)]
This canonical form ((0,0), (0,1), (1,0))
already exists in our set!
Step 5: Final Result Set remains: {((0,0), (0,1), (1,0))} Count of distinct islands: 1
Even though we found two islands in different orientations, the normalization process correctly identified them as the same shape, giving us the correct count of 1 distinct island.
Solution Implementation
1class Solution:
2 def numDistinctIslands2(self, grid: List[List[int]]) -> int:
3 def dfs(row: int, col: int, island_cells: List[List[int]]) -> None:
4 """
5 Depth-first search to explore an island and collect all its cells.
6 Marks visited cells as 0 to avoid revisiting.
7 """
8 # Add current cell to the island shape
9 island_cells.append([row, col])
10 # Mark as visited
11 grid[row][col] = 0
12
13 # Explore all 4 directions
14 directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
15 for delta_row, delta_col in directions:
16 next_row = row + delta_row
17 next_col = col + delta_col
18 # Check bounds and if cell is part of island
19 if (0 <= next_row < rows and
20 0 <= next_col < cols and
21 grid[next_row][next_col] == 1):
22 dfs(next_row, next_col, island_cells)
23
24 def normalize(island_cells: List[List[int]]) -> Tuple[Tuple[int, ...]]:
25 """
26 Generate all 8 possible transformations (rotations and reflections)
27 of an island and return the lexicographically smallest one as canonical form.
28 """
29 # Generate all 8 transformations (4 rotations Γ 2 reflections)
30 transformations = [[] for _ in range(8)]
31
32 for row, col in island_cells:
33 # Original and reflections
34 transformations[0].append([row, col]) # Original
35 transformations[1].append([row, -col]) # Reflect over y-axis
36 transformations[2].append([-row, col]) # Reflect over x-axis
37 transformations[3].append([-row, -col]) # Rotate 180Β°
38 # 90Β° rotation and its reflections
39 transformations[4].append([col, row]) # Rotate 90Β° CCW
40 transformations[5].append([col, -row]) # Rotate 90Β° CCW + reflect
41 transformations[6].append([-col, row]) # Rotate 90Β° CW + reflect
42 transformations[7].append([-col, -row]) # Rotate 90Β° CW
43
44 # Normalize each transformation by translating to origin
45 for transformation in transformations:
46 # Sort cells to ensure consistent ordering
47 transformation.sort()
48 # Translate so that first cell is at origin (0, 0)
49 base_row = transformation[0][0]
50 base_col = transformation[0][1]
51 for idx in range(len(transformation) - 1, -1, -1):
52 transformation[idx][0] -= base_row
53 transformation[idx][1] -= base_col
54
55 # Sort all transformations and pick the lexicographically smallest
56 transformations.sort()
57
58 # Convert to tuple for hashability
59 return tuple(tuple(cell) for cell in transformations[0])
60
61 # Initialize grid dimensions
62 rows, cols = len(grid), len(grid[0])
63
64 # Set to store unique island shapes
65 unique_islands = set()
66
67 # Explore the entire grid
68 for row in range(rows):
69 for col in range(cols):
70 if grid[row][col] == 1:
71 # Found an unvisited island
72 island_cells = []
73 dfs(row, col, island_cells)
74 # Normalize and add to set of unique islands
75 canonical_form = normalize(island_cells)
76 unique_islands.add(canonical_form)
77
78 return len(unique_islands)
79
1class Solution {
2 private int rows;
3 private int cols;
4 private int[][] grid;
5
6 /**
7 * Counts the number of distinct islands considering rotations and reflections.
8 * Two islands are considered the same if one can be rotated and/or reflected to match the other.
9 *
10 * @param grid 2D binary grid where 1 represents land and 0 represents water
11 * @return number of distinct islands
12 */
13 public int numDistinctIslands2(int[][] grid) {
14 rows = grid.length;
15 cols = grid[0].length;
16 this.grid = grid;
17
18 // Set to store unique normalized island shapes
19 Set<List<List<Integer>>> uniqueIslands = new HashSet<>();
20
21 // Traverse the grid to find all islands
22 for (int i = 0; i < rows; ++i) {
23 for (int j = 0; j < cols; ++j) {
24 if (grid[i][j] == 1) {
25 // Found an unvisited island cell
26 List<Integer> islandCells = new ArrayList<>();
27 dfs(i, j, islandCells);
28 // Normalize the island shape and add to set
29 uniqueIslands.add(normalize(islandCells));
30 }
31 }
32 }
33
34 return uniqueIslands.size();
35 }
36
37 /**
38 * Normalizes an island shape by considering all 8 possible transformations
39 * (4 rotations Γ 2 reflections) and returns the lexicographically smallest one.
40 *
41 * @param islandCells list of encoded cell positions (row * cols + col)
42 * @return normalized representation of the island shape
43 */
44 private List<List<Integer>> normalize(List<Integer> islandCells) {
45 // Array to store all 8 possible transformations
46 List<int[]>[] transformations = new List[8];
47 for (int i = 0; i < 8; ++i) {
48 transformations[i] = new ArrayList<>();
49 }
50
51 // Generate all 8 transformations for each cell
52 for (int encodedPosition : islandCells) {
53 int row = encodedPosition / cols;
54 int col = encodedPosition % cols;
55
56 // 8 transformations: original, reflections, and rotations
57 transformations[0].add(new int[] {row, col}); // Original
58 transformations[1].add(new int[] {row, -col}); // Reflect horizontally
59 transformations[2].add(new int[] {-row, col}); // Reflect vertically
60 transformations[3].add(new int[] {-row, -col}); // Rotate 180Β°
61 transformations[4].add(new int[] {col, row}); // Transpose (rotate 90Β° + reflect)
62 transformations[5].add(new int[] {col, -row}); // Rotate 90Β° clockwise
63 transformations[6].add(new int[] {-col, row}); // Rotate 90Β° counter-clockwise
64 transformations[7].add(new int[] {-col, -row}); // Rotate 270Β° clockwise
65 }
66
67 // Normalize each transformation
68 for (List<int[]> transformation : transformations) {
69 // Sort cells by row, then by column
70 transformation.sort((cell1, cell2) -> {
71 int row1 = cell1[0];
72 int col1 = cell1[1];
73 int row2 = cell2[0];
74 int col2 = cell2[1];
75 if (row1 == row2) {
76 return col1 - col2;
77 }
78 return row1 - row2;
79 });
80
81 // Translate the shape so that the top-left cell is at origin (0, 0)
82 int minRow = transformation.get(0)[0];
83 int minCol = transformation.get(0)[1];
84 for (int k = transformation.size() - 1; k >= 0; --k) {
85 int row = transformation.get(k)[0];
86 int col = transformation.get(k)[1];
87 transformation.set(k, new int[] {row - minRow, col - minCol});
88 }
89 }
90
91 // Sort all transformations to find the lexicographically smallest one
92 Arrays.sort(transformations, (transformation1, transformation2) -> {
93 for (int k = 0; k < transformation1.size(); ++k) {
94 int row1 = transformation1.get(k)[0];
95 int col1 = transformation1.get(k)[1];
96 int row2 = transformation2.get(k)[0];
97 int col2 = transformation2.get(k)[1];
98 if (row1 != row2) {
99 return row1 - row2;
100 }
101 if (col1 != col2) {
102 return col1 - col2;
103 }
104 }
105 return 0;
106 });
107
108 // Convert the smallest transformation to the required format
109 List<List<Integer>> normalizedShape = new ArrayList<>();
110 for (int[] cell : transformations[0]) {
111 normalizedShape.add(Arrays.asList(cell[0], cell[1]));
112 }
113
114 return normalizedShape;
115 }
116
117 /**
118 * Performs depth-first search to explore all cells of an island.
119 * Marks visited cells as 0 to avoid revisiting.
120 *
121 * @param row current row position
122 * @param col current column position
123 * @param islandCells list to store all cells of the current island
124 */
125 private void dfs(int row, int col, List<Integer> islandCells) {
126 // Add current cell to the island (encode position as single integer)
127 islandCells.add(row * cols + col);
128
129 // Mark cell as visited
130 grid[row][col] = 0;
131
132 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
133 int[] directions = {-1, 0, 1, 0, -1};
134
135 // Explore all 4 adjacent cells
136 for (int k = 0; k < 4; ++k) {
137 int nextRow = row + directions[k];
138 int nextCol = col + directions[k + 1];
139
140 // Check if the adjacent cell is valid and is part of the island
141 if (nextRow >= 0 && nextRow < rows &&
142 nextCol >= 0 && nextCol < cols &&
143 grid[nextRow][nextCol] == 1) {
144 dfs(nextRow, nextCol, islandCells);
145 }
146 }
147 }
148}
149
1typedef pair<int, int> PII;
2
3class Solution {
4public:
5 int numDistinctIslands2(vector<vector<int>>& grid) {
6 set<vector<PII>> uniqueIslands;
7
8 // Traverse the entire grid to find all islands
9 for (int row = 0; row < grid.size(); ++row) {
10 for (int col = 0; col < grid[0].size(); ++col) {
11 if (grid[row][col] == 1) {
12 vector<PII> islandShape;
13 // Perform DFS to collect all cells of the current island
14 dfs(row, col, grid, islandShape);
15 // Normalize the island shape to handle rotations and reflections
16 uniqueIslands.insert(normalize(islandShape));
17 }
18 }
19 }
20
21 return uniqueIslands.size();
22 }
23
24private:
25 // Normalize the island shape by considering all 8 possible transformations
26 // (4 rotations Γ 2 reflections)
27 vector<PII> normalize(vector<PII>& shape) {
28 vector<vector<PII>> allTransformations(8);
29
30 // Generate all 8 transformations of the island
31 for (const auto& point : shape) {
32 int x = point.first;
33 int y = point.second;
34
35 // Original and reflections
36 allTransformations[0].push_back({x, y}); // Original
37 allTransformations[1].push_back({x, -y}); // Reflect along x-axis
38 allTransformations[2].push_back({-x, y}); // Reflect along y-axis
39 allTransformations[3].push_back({-x, -y}); // Rotate 180Β°
40
41 // 90Β° rotation and its reflections
42 allTransformations[4].push_back({y, x}); // Rotate 90Β° counterclockwise
43 allTransformations[5].push_back({y, -x}); // Rotate 90Β° + reflect
44 allTransformations[6].push_back({-y, -x}); // Rotate 270Β° counterclockwise
45 allTransformations[7].push_back({-y, x}); // Rotate 270Β° + reflect
46 }
47
48 // Normalize each transformation
49 for (auto& transformation : allTransformations) {
50 // Sort points to ensure consistent ordering
51 sort(transformation.begin(), transformation.end());
52
53 // Translate all points so that the first point is at origin (0, 0)
54 int baseX = transformation[0].first;
55 int baseY = transformation[0].second;
56 for (int i = transformation.size() - 1; i >= 0; --i) {
57 transformation[i].first -= baseX;
58 transformation[i].second -= baseY;
59 }
60 }
61
62 // Sort all transformations and return the lexicographically smallest one
63 sort(allTransformations.begin(), allTransformations.end());
64 return allTransformations[0];
65 }
66
67 // DFS to explore and mark all cells belonging to the current island
68 void dfs(int row, int col, vector<vector<int>>& grid, vector<PII>& shape) {
69 // Add current cell to the island shape
70 shape.push_back({row, col});
71
72 // Mark current cell as visited by setting it to 0
73 grid[row][col] = 0;
74
75 // Direction vectors for up, right, down, left
76 vector<int> directions = {-1, 0, 1, 0, -1};
77
78 // Explore all 4 adjacent cells
79 for (int dir = 0; dir < 4; ++dir) {
80 int newRow = row + directions[dir];
81 int newCol = col + directions[dir + 1];
82
83 // Check if the adjacent cell is valid and is part of the island
84 if (newRow >= 0 && newRow < grid.size() &&
85 newCol >= 0 && newCol < grid[0].size() &&
86 grid[newRow][newCol] == 1) {
87 dfs(newRow, newCol, grid, shape);
88 }
89 }
90 }
91};
92
1// Type definition for a coordinate pair
2type PII = [number, number];
3
4function numDistinctIslands2(grid: number[][]): number {
5 const uniqueIslands = new Set<string>();
6
7 // Traverse the entire grid to find all islands
8 for (let row = 0; row < grid.length; row++) {
9 for (let col = 0; col < grid[0].length; col++) {
10 if (grid[row][col] === 1) {
11 const islandShape: PII[] = [];
12 // Perform DFS to collect all cells of the current island
13 dfs(row, col, grid, islandShape);
14 // Normalize the island shape to handle rotations and reflections
15 // Convert to string for Set comparison
16 uniqueIslands.add(JSON.stringify(normalize(islandShape)));
17 }
18 }
19 }
20
21 return uniqueIslands.size;
22}
23
24// Normalize the island shape by considering all 8 possible transformations
25// (4 rotations Γ 2 reflections)
26function normalize(shape: PII[]): PII[] {
27 const allTransformations: PII[][] = Array(8).fill(null).map(() => []);
28
29 // Generate all 8 transformations of the island
30 for (const point of shape) {
31 const x = point[0];
32 const y = point[1];
33
34 // Original and reflections
35 allTransformations[0].push([x, y]); // Original
36 allTransformations[1].push([x, -y]); // Reflect along x-axis
37 allTransformations[2].push([-x, y]); // Reflect along y-axis
38 allTransformations[3].push([-x, -y]); // Rotate 180Β°
39
40 // 90Β° rotation and its reflections
41 allTransformations[4].push([y, x]); // Rotate 90Β° counterclockwise
42 allTransformations[5].push([y, -x]); // Rotate 90Β° + reflect
43 allTransformations[6].push([-y, -x]); // Rotate 270Β° counterclockwise
44 allTransformations[7].push([-y, x]); // Rotate 270Β° + reflect
45 }
46
47 // Normalize each transformation
48 for (const transformation of allTransformations) {
49 // Sort points to ensure consistent ordering
50 transformation.sort((a, b) => {
51 if (a[0] !== b[0]) return a[0] - b[0];
52 return a[1] - b[1];
53 });
54
55 // Translate all points so that the first point is at origin (0, 0)
56 const baseX = transformation[0][0];
57 const baseY = transformation[0][1];
58 for (let i = transformation.length - 1; i >= 0; i--) {
59 transformation[i][0] -= baseX;
60 transformation[i][1] -= baseY;
61 }
62 }
63
64 // Sort all transformations and return the lexicographically smallest one
65 allTransformations.sort((a, b) => {
66 // Compare transformation arrays lexicographically
67 const minLength = Math.min(a.length, b.length);
68 for (let i = 0; i < minLength; i++) {
69 if (a[i][0] !== b[i][0]) return a[i][0] - b[i][0];
70 if (a[i][1] !== b[i][1]) return a[i][1] - b[i][1];
71 }
72 return a.length - b.length;
73 });
74
75 return allTransformations[0];
76}
77
78// DFS to explore and mark all cells belonging to the current island
79function dfs(row: number, col: number, grid: number[][], shape: PII[]): void {
80 // Add current cell to the island shape
81 shape.push([row, col]);
82
83 // Mark current cell as visited by setting it to 0
84 grid[row][col] = 0;
85
86 // Direction vectors for up, right, down, left
87 const directions = [-1, 0, 1, 0, -1];
88
89 // Explore all 4 adjacent cells
90 for (let dir = 0; dir < 4; dir++) {
91 const newRow = row + directions[dir];
92 const newCol = col + directions[dir + 1];
93
94 // Check if the adjacent cell is valid and is part of the island
95 if (newRow >= 0 && newRow < grid.length &&
96 newCol >= 0 && newCol < grid[0].length &&
97 grid[newRow][newCol] === 1) {
98 dfs(newRow, newCol, grid, shape);
99 }
100 }
101}
102
Time and Space Complexity
Time Complexity: O(m * n * k * log(k))
where m
and n
are the dimensions of the grid, and k
is the size of the largest island.
- The outer loops iterate through all cells in the grid:
O(m * n)
- For each cell that starts an island, we perform DFS which visits all cells in that island:
O(k)
wherek
is the island size - The
normalize
function is called for each island:- Creating 8 transformations:
O(8 * k) = O(k)
- Sorting each of the 8 transformations:
O(8 * k * log(k)) = O(k * log(k))
- Normalizing coordinates by subtracting the first element:
O(8 * k) = O(k)
- Sorting the 8 transformations to find the canonical form:
O(8 * log(8) * k) = O(k)
(comparing lists of sizek
) - Converting to tuple:
O(k)
- Overall normalize:
O(k * log(k))
- Creating 8 transformations:
- In the worst case where we have
O(m * n / k)
islands, the total time is:O((m * n / k) * k * log(k)) = O(m * n * log(k))
- However, when considering all operations including DFS traversal for all cells:
O(m * n * k * log(k))
Space Complexity: O(m * n)
- The
shape
list stores coordinates for one island at a time:O(k)
wherek
is the island size - The
shapes
list in normalize stores 8 transformations:O(8 * k) = O(k)
- The set
s
stores unique normalized island shapes. In the worst case, every cell could be a separate island:O(m * n)
- The recursion stack depth for DFS can be at most
O(m * n)
in the worst case of a snake-like island - Overall space complexity:
O(m * n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Input Grid Without Restoration
The current solution modifies the input grid by setting visited cells to 0. This destructive approach can be problematic if:
- The grid needs to be preserved for other operations
- Multiple test cases run on the same grid instance
- The problem explicitly states not to modify the input
Solution: Use a separate visited set to track explored cells instead of modifying the grid:
def dfs(row: int, col: int, island_cells: List[List[int]], visited: Set[Tuple[int, int]]) -> None:
island_cells.append([row, col])
visited.add((row, col))
for delta_row, delta_col in [[1, 0], [-1, 0], [0, 1], [0, -1]]:
next_row = row + delta_row
next_col = col + delta_col
if (0 <= next_row < rows and
0 <= next_col < cols and
grid[next_row][next_col] == 1 and
(next_row, next_col) not in visited):
dfs(next_row, next_col, island_cells, visited)
# In main loop:
visited = set()
for row in range(rows):
for col in range(cols):
if grid[row][col] == 1 and (row, col) not in visited:
island_cells = []
dfs(row, col, island_cells, visited)
unique_islands.add(normalize(island_cells))
2. Incorrect Transformation Logic
A subtle but critical pitfall is incorrectly implementing the 8 transformations. The transformations must correctly represent all possible rotations and reflections:
Common mistakes:
- Missing some transformations (e.g., only doing 4 instead of 8)
- Incorrect rotation formulas (mixing up clockwise vs counter-clockwise)
- Not handling both horizontal and vertical reflections
Solution: Verify transformations with a simple test case. For a point (x, y):
- 90Β° CCW rotation: (-y, x)
- 90Β° CW rotation: (y, -x)
- 180Β° rotation: (-x, -y)
- Horizontal reflection: (x, -y)
- Vertical reflection: (-x, y)
3. Inefficient Normalization Process
The current implementation sorts transformations multiple times and creates many intermediate lists, which can be memory-intensive for large islands.
Solution: Optimize by finding the canonical form more efficiently:
def normalize(island_cells: List[List[int]]) -> Tuple[Tuple[int, ...]]:
def get_canonical(cells):
# Sort and translate to origin in one pass
cells = sorted(cells)
if not cells:
return tuple()
base_row, base_col = cells[0]
return tuple((r - base_row, c - base_col) for r, c in cells)
# Generate and compare transformations without storing all of them
min_shape = None
for transform_func in [
lambda r, c: (r, c), # Original
lambda r, c: (r, -c), # Reflect y
lambda r, c: (-r, c), # Reflect x
lambda r, c: (-r, -c), # Rotate 180
lambda r, c: (c, r), # Rotate 90 CCW
lambda r, c: (c, -r), # Rotate 90 CCW + reflect
lambda r, c: (-c, r), # Rotate 270 CCW
lambda r, c: (-c, -r), # Rotate 270
]:
transformed = [transform_func(r, c) for r, c in island_cells]
canonical = get_canonical(transformed)
if min_shape is None or canonical < min_shape:
min_shape = canonical
return min_shape
4. Stack Overflow with Large Islands
For very large islands, the recursive DFS can cause stack overflow.
Solution: Use iterative DFS with an explicit stack:
def dfs_iterative(start_row: int, start_col: int) -> List[List[int]]:
island_cells = []
stack = [(start_row, start_col)]
visited.add((start_row, start_col))
while stack:
row, col = stack.pop()
island_cells.append([row, col])
for delta_row, delta_col in [[1, 0], [-1, 0], [0, 1], [0, -1]]:
next_row = row + delta_row
next_col = col + delta_col
if (0 <= next_row < rows and
0 <= next_col < cols and
grid[next_row][next_col] == 1 and
(next_row, next_col) not in visited):
visited.add((next_row, next_col))
stack.append((next_row, next_col))
return island_cells
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
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!