934. Shortest Bridge
Problem Description
You have an n x n
binary matrix called grid
where:
1
represents land0
represents water
An island is defined as a group of 1
s that are connected horizontally or vertically (4-directionally connected). These 1
s are not connected to any other group of 1
s outside the island.
The grid contains exactly two islands.
Your task is to connect these two islands into one single island by changing some 0
s (water) to 1
s (land). You want to find the minimum number of 0
s that need to be changed to 1
s to create a bridge between the two islands.
For example, if you have two islands separated by water, you need to find the shortest path of water cells between them and convert those water cells to land to connect the islands.
The function should return the smallest number of 0
s you must flip to connect the two 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 cells can be connected to adjacent cells (4-directionally). This forms an implicit graph where each cell is a node and edges exist between adjacent cells.
Is it a tree?
- No: The grid structure is not a tree. We have islands (connected components) and we need to find connections between them. The grid can have cycles and multiple disconnected components.
Is the problem related to directed acyclic graphs?
- No: The grid represents an undirected graph where connections between adjacent cells are bidirectional.
Is the problem related to shortest paths?
- Yes: We need to find the minimum number of water cells (0s) to flip to connect two islands, which is essentially finding the shortest path between the two islands.
Is the graph weighted?
- No: Each step from one water cell to another has the same cost (flipping one 0 to 1), making this an unweighted shortest path problem.
BFS
- Following the "no" branch from weighted graphs leads us to BFS for unweighted shortest path problems.
However, the solution also uses DFS initially to:
- Find and mark the first island (changing all its 1s to 2s)
- Collect all cells of the first island as starting points for the BFS
Conclusion: The flowchart suggests using BFS for finding the shortest path between two islands. The actual solution combines DFS (to identify and mark one island) followed by BFS (to find the minimum distance to the second island). This hybrid approach efficiently solves the shortest bridge problem.
Intuition
To connect two islands with the minimum number of flips, we need to find the shortest distance between them. Think of this problem like building the shortest bridge between two pieces of land separated by water.
The key insight is that we can treat this as a multi-source BFS problem. Here's the thought process:
-
First, we need to distinguish the two islands. If we start BFS from random cells, we won't know which island they belong to. So we use DFS to find one complete island first and mark all its cells differently (say, change all
1
s to2
s). This way, we know exactly where one island is. -
Why start from the entire island edge? Instead of picking a single cell from the first island and finding the shortest path to the second island, we can be smarter. We treat all cells of the first island as starting points simultaneously. This is like expanding outward from the entire perimeter of the first island at once.
-
The expansion process: We perform a level-by-level BFS expansion from all cells of the first island. Each level represents one additional water cell we need to flip. We expand outward like ripples in water:
- Level 0: All cells of the first island
- Level 1: All water cells adjacent to the first island
- Level 2: Water cells one step further out
- And so on...
-
When do we stop? The moment our expansion reaches any cell of the second island (a cell with value
1
), we've found the shortest bridge. The number of levels we've expanded equals the minimum number of water cells we need to flip.
This approach is optimal because BFS guarantees we find the shortest path in an unweighted graph, and by starting from all cells of one island simultaneously, we ensure we find the absolute minimum distance between any two points on different islands.
Learn more about Depth-First Search and Breadth-First Search patterns.
Solution Approach
The implementation uses a combination of DFS and multi-source BFS to solve the problem efficiently:
Step 1: Find and Mark the First Island
i, j = next((i, j) for i in range(n) for j in range(n) if grid[i][j])
dfs(i, j)
- We iterate through the grid to find the first cell with value
1
- Once found, we call DFS from that cell to explore the entire first island
Step 2: DFS to Mark and Collect Island Cells
def dfs(i, j):
q.append((i, j))
grid[i][j] = 2
for a, b in pairwise(dirs):
x, y = i + a, j + b
if 0 <= x < n and 0 <= y < n and grid[x][y] == 1:
dfs(x, y)
- The DFS function marks each visited cell of the first island as
2
(to distinguish it from the second island) - It adds each cell to a queue
q
which will serve as the starting points for BFS - Uses
pairwise(dirs)
wheredirs = (-1, 0, 1, 0, -1)
to generate the 4 directions: up, right, down, left
Step 3: Multi-source BFS to Find Shortest Bridge
ans = 0
while 1:
for _ in range(len(q)):
i, j = q.popleft()
for a, b in pairwise(dirs):
x, y = i + a, j + b
if 0 <= x < n and 0 <= y < n:
if grid[x][y] == 1:
return ans
if grid[x][y] == 0:
grid[x][y] = 2
q.append((x, y))
ans += 1
The BFS expansion works level by level:
ans
tracks the current distance (number of water cells to flip)- For each level, we process all cells currently in the queue
- For each cell, we check its 4 neighbors:
- If we find a cell with value
1
(the second island), we immediately returnans
as the minimum distance - If we find a cell with value
0
(water), we mark it as2
(visited) and add it to the queue for the next level - Cells already marked as
2
are skipped to avoid revisiting
- If we find a cell with value
- After processing each level, we increment
ans
to represent moving one step further from the first island
Key Data Structures:
deque
: Used as a queue for BFS to efficiently add/remove elements from both endsgrid
: Modified in-place to mark visited cells, avoiding the need for a separate visited set
Time Complexity: O(nΒ²)
where n is the grid dimension, as we potentially visit every cell once
Space Complexity: O(nΒ²)
for the queue in the worst case where one island occupies most of the grid
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.
Consider this 4Γ4 grid with two islands:
Initial Grid: 1 1 0 0 1 0 0 0 0 0 0 1 0 0 1 1
Step 1: Find and Mark the First Island using DFS
Starting from position (0,0), we use DFS to explore and mark the entire first island:
- Visit (0,0): mark as 2, add to queue
- Visit (0,1): mark as 2, add to queue
- Visit (1,0): mark as 2, add to queue
After DFS: 2 2 0 0 2 0 0 0 0 0 0 1 0 0 1 1 Queue q = [(0,0), (0,1), (1,0)]
Step 2: Multi-source BFS Expansion
Now we perform BFS starting from all cells of the first island simultaneously.
Level 0 (ans = 0): Process the initial queue containing the first island's cells.
- From (0,0): Check neighbors β (0,1) is 2 (skip), (1,0) is 2 (skip)
- From (0,1): Check neighbors β (0,0) is 2 (skip), (0,2) is 0 (mark as 2, add to queue), (1,1) is 0 (mark as 2, add to queue)
- From (1,0): Check neighbors β (0,0) is 2 (skip), (1,1) already marked as 2 (skip), (2,0) is 0 (mark as 2, add to queue)
After Level 0: 2 2 2 0 2 2 0 0 2 0 0 1 0 0 1 1 New queue = [(0,2), (1,1), (2,0)] ans = 1 (increment for next level)
Level 1 (ans = 1): Process water cells adjacent to the first island.
- From (0,2): Check neighbors β (0,1) is 2 (skip), (0,3) is 0 (mark as 2, add to queue), (1,2) is 0 (mark as 2, add to queue)
- From (1,1): Check neighbors β all are 2 or already processed
- From (2,0): Check neighbors β (1,0) is 2 (skip), (2,1) is 0 (mark as 2, add to queue), (3,0) is 0 (mark as 2, add to queue)
After Level 1: 2 2 2 2 2 2 2 0 2 2 0 1 2 0 1 1 New queue = [(0,3), (1,2), (2,1), (3,0)] ans = 2 (increment for next level)
Level 2 (ans = 2): Continue expansion.
- From (0,3): Check neighbors β (1,3) is 0 (mark as 2, add to queue)
- From (1,2): Check neighbors β (1,3) already marked, (2,2) is 0 (mark as 2, add to queue)
- From (2,1): Check neighbors β (2,2) already marked, (3,1) is 0 (mark as 2, add to queue)
- From (3,0): Check neighbors β (3,1) already marked
After Level 2: 2 2 2 2 2 2 2 2 2 2 2 1 2 2 1 1 New queue = [(1,3), (2,2), (3,1)] ans = 3 (increment for next level)
Level 3 (ans = 3):
- From (1,3): Check neighbors β all are 2
- From (2,2): Check neighbors β (2,3) is 1 (second island found!)
Return ans = 3
The algorithm found that we need to flip 3 water cells to connect the two islands. The shortest bridge would be through cells (1,1) β (1,2) β (2,2), converting these 3 water cells to land to connect the islands.
Solution Implementation
1class Solution:
2 def shortestBridge(self, grid: List[List[int]]) -> int:
3 """
4 Find the shortest bridge (minimum flips) needed to connect two islands.
5 Strategy:
6 1. Find and mark the first island using DFS
7 2. Use BFS to expand from the first island until we reach the second island
8 """
9
10 def dfs(row: int, col: int) -> None:
11 """
12 Depth-first search to mark all cells of the first island.
13 Marks cells as 2 and adds them to the queue for BFS.
14 """
15 # Add current cell to queue for BFS later
16 queue.append((row, col))
17 # Mark this cell as visited (part of first island)
18 grid[row][col] = 2
19
20 # Explore all 4 directions
21 for dx, dy in directions:
22 next_row = row + dx
23 next_col = col + dy
24
25 # Check bounds and if it's part of the same island
26 if (0 <= next_row < n and
27 0 <= next_col < n and
28 grid[next_row][next_col] == 1):
29 dfs(next_row, next_col)
30
31 # Initialize variables
32 n = len(grid)
33 # Direction vectors for up, right, down, left
34 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
35 queue = deque()
36
37 # Find the first island's starting point
38 start_row, start_col = next(
39 (i, j) for i in range(n) for j in range(n) if grid[i][j] == 1
40 )
41
42 # Mark entire first island and populate queue
43 dfs(start_row, start_col)
44
45 # BFS to find shortest path to second island
46 distance = 0
47 while True:
48 # Process all cells at current distance level
49 for _ in range(len(queue)):
50 row, col = queue.popleft()
51
52 # Check all 4 adjacent cells
53 for dx, dy in directions:
54 next_row = row + dx
55 next_col = col + dy
56
57 # Check if within bounds
58 if 0 <= next_row < n and 0 <= next_col < n:
59 # Found the second island
60 if grid[next_row][next_col] == 1:
61 return distance
62
63 # Found water, mark it and add to queue
64 if grid[next_row][next_col] == 0:
65 grid[next_row][next_col] = 2
66 queue.append((next_row, next_col))
67
68 # Increment distance for next level
69 distance += 1
70
1class Solution {
2 // Direction vectors for moving up, right, down, left
3 private int[] directions = {-1, 0, 1, 0, -1};
4 // Queue for BFS to find shortest path between islands
5 private Deque<int[]> queue = new ArrayDeque<>();
6 // Reference to the grid
7 private int[][] grid;
8 // Grid dimension (n x n)
9 private int n;
10
11 /**
12 * Finds the shortest bridge to connect two islands in the grid.
13 * Strategy:
14 * 1. Find the first island using DFS and mark all its cells as 2
15 * 2. Use BFS from all cells of the first island to find the shortest path to the second island
16 *
17 * @param grid 2D array where 1 represents land and 0 represents water
18 * @return The minimum number of 0s that must be flipped to connect the two islands
19 */
20 public int shortestBridge(int[][] grid) {
21 this.grid = grid;
22 this.n = grid.length;
23
24 // Find the first island and mark it
25 boolean foundFirstIsland = false;
26 for (int i = 0; i < n && !foundFirstIsland; i++) {
27 for (int j = 0; j < n; j++) {
28 if (grid[i][j] == 1) {
29 // Mark the entire first island as 2 and add its cells to queue
30 dfs(i, j);
31 foundFirstIsland = true;
32 break;
33 }
34 }
35 }
36
37 // BFS to find shortest path from first island to second island
38 int distance = 0;
39 while (true) {
40 // Process all cells at current distance level
41 int currentLevelSize = queue.size();
42 for (int i = 0; i < currentLevelSize; i++) {
43 int[] currentCell = queue.pollFirst();
44
45 // Check all 4 adjacent cells
46 for (int k = 0; k < 4; k++) {
47 int nextX = currentCell[0] + directions[k];
48 int nextY = currentCell[1] + directions[k + 1];
49
50 // Check if the next cell is within bounds
51 if (nextX >= 0 && nextX < n && nextY >= 0 && nextY < n) {
52 if (grid[nextX][nextY] == 1) {
53 // Found the second island
54 return distance;
55 }
56 if (grid[nextX][nextY] == 0) {
57 // Mark water cell as visited and add to queue
58 grid[nextX][nextY] = 2;
59 queue.offer(new int[] {nextX, nextY});
60 }
61 }
62 }
63 }
64 // Increment distance after processing all cells at current level
65 distance++;
66 }
67 }
68
69 /**
70 * DFS to mark all cells of the first island as 2 and add them to the queue.
71 * This prepares for the BFS phase to find the shortest path to the second island.
72 *
73 * @param i Row index of current cell
74 * @param j Column index of current cell
75 */
76 private void dfs(int i, int j) {
77 // Mark current cell as part of first island (visited)
78 grid[i][j] = 2;
79 // Add current cell to queue for BFS starting points
80 queue.offer(new int[] {i, j});
81
82 // Explore all 4 adjacent cells
83 for (int k = 0; k < 4; k++) {
84 int nextX = i + directions[k];
85 int nextY = j + directions[k + 1];
86
87 // Continue DFS if adjacent cell is within bounds and part of the same island
88 if (nextX >= 0 && nextX < n && nextY >= 0 && nextY < n && grid[nextX][nextY] == 1) {
89 dfs(nextX, nextY);
90 }
91 }
92 }
93}
94
1class Solution {
2public:
3 // Direction vectors for moving up, right, down, left
4 const static inline vector<int> directions = {-1, 0, 1, 0, -1};
5
6 int shortestBridge(vector<vector<int>>& grid) {
7 int n = grid.size();
8 queue<pair<int, int>> bfsQueue;
9
10 // DFS function to mark all cells of the first island as 2 and add them to BFS queue
11 function<void(int, int)> markFirstIsland = [&](int row, int col) {
12 // Mark current cell as visited (part of first island)
13 grid[row][col] = 2;
14 // Add to BFS queue for later expansion
15 bfsQueue.emplace(row, col);
16
17 // Explore all 4 directions
18 for (int dir = 0; dir < 4; ++dir) {
19 int newRow = row + directions[dir];
20 int newCol = col + directions[dir + 1];
21
22 // Check boundaries and if the cell belongs to the same island
23 if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n &&
24 grid[newRow][newCol] == 1) {
25 markFirstIsland(newRow, newCol);
26 }
27 }
28 };
29
30 // Find and mark the first island
31 bool foundFirstIsland = false;
32 for (int i = 0; i < n && !foundFirstIsland; ++i) {
33 for (int j = 0; j < n; ++j) {
34 if (grid[i][j] == 1) {
35 // Found first island, mark all its cells as 2
36 markFirstIsland(i, j);
37 foundFirstIsland = true;
38 break;
39 }
40 }
41 }
42
43 // BFS to find shortest path to the second island
44 int distance = 0;
45 while (true) {
46 int currentLevelSize = bfsQueue.size();
47
48 // Process all cells at current distance level
49 for (int i = 0; i < currentLevelSize; ++i) {
50 auto [row, col] = bfsQueue.front();
51 bfsQueue.pop();
52
53 // Check all 4 directions
54 for (int dir = 0; dir < 4; ++dir) {
55 int newRow = row + directions[dir];
56 int newCol = col + directions[dir + 1];
57
58 // Check boundaries
59 if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {
60 // Found the second island
61 if (grid[newRow][newCol] == 1) {
62 return distance;
63 }
64
65 // Found water, mark as visited and add to queue
66 if (grid[newRow][newCol] == 0) {
67 grid[newRow][newCol] = 2;
68 bfsQueue.emplace(newRow, newCol);
69 }
70 }
71 }
72 }
73 // Increment distance for next level
74 ++distance;
75 }
76 }
77};
78
1// Direction vectors for moving up, right, down, left
2const directions: number[] = [-1, 0, 1, 0, -1];
3
4function shortestBridge(grid: number[][]): number {
5 const n: number = grid.length;
6 const bfsQueue: [number, number][] = [];
7
8 // DFS function to mark all cells of the first island as 2 and add them to BFS queue
9 const markFirstIsland = (row: number, col: number): void => {
10 // Mark current cell as visited (part of first island)
11 grid[row][col] = 2;
12 // Add to BFS queue for later expansion
13 bfsQueue.push([row, col]);
14
15 // Explore all 4 directions
16 for (let dir = 0; dir < 4; dir++) {
17 const newRow: number = row + directions[dir];
18 const newCol: number = col + directions[dir + 1];
19
20 // Check boundaries and if the cell belongs to the same island
21 if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n &&
22 grid[newRow][newCol] === 1) {
23 markFirstIsland(newRow, newCol);
24 }
25 }
26 };
27
28 // Find and mark the first island
29 let foundFirstIsland: boolean = false;
30 for (let i = 0; i < n && !foundFirstIsland; i++) {
31 for (let j = 0; j < n; j++) {
32 if (grid[i][j] === 1) {
33 // Found first island, mark all its cells as 2
34 markFirstIsland(i, j);
35 foundFirstIsland = true;
36 break;
37 }
38 }
39 }
40
41 // BFS to find shortest path to the second island
42 let distance: number = 0;
43 while (true) {
44 const currentLevelSize: number = bfsQueue.length;
45
46 // Process all cells at current distance level
47 for (let i = 0; i < currentLevelSize; i++) {
48 const [row, col] = bfsQueue.shift()!;
49
50 // Check all 4 directions
51 for (let dir = 0; dir < 4; dir++) {
52 const newRow: number = row + directions[dir];
53 const newCol: number = col + directions[dir + 1];
54
55 // Check boundaries
56 if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {
57 // Found the second island
58 if (grid[newRow][newCol] === 1) {
59 return distance;
60 }
61
62 // Found water, mark as visited and add to queue
63 if (grid[newRow][newCol] === 0) {
64 grid[newRow][newCol] = 2;
65 bfsQueue.push([newRow, newCol]);
66 }
67 }
68 }
69 }
70 // Increment distance for next level
71 distance++;
72 }
73}
74
Time and Space Complexity
Time Complexity: O(nΒ²)
The algorithm consists of two main phases:
-
DFS phase: Finding and marking the first island
- The DFS traverses all cells of the first island exactly once
- In the worst case, the island could contain all
nΒ²
cells - Time:
O(nΒ²)
-
BFS phase: Expanding from the first island to find the second island
- The BFS explores cells level by level from the first island
- Each cell in the grid is visited at most once during BFS
- In the worst case, we might need to explore nearly all
nΒ²
cells before reaching the second island - Time:
O(nΒ²)
Overall time complexity: O(nΒ²) + O(nΒ²) = O(nΒ²)
Space Complexity: O(nΒ²)
The space usage comes from:
-
Recursion stack for DFS:
- In the worst case, if the island forms a long path, the recursion depth could be
O(nΒ²)
- In the worst case, if the island forms a long path, the recursion depth could be
-
BFS queue:
- In the worst case, the queue could contain all cells of the first island
- Maximum queue size:
O(nΒ²)
-
Grid modification:
- The algorithm modifies the input grid in-place, so no additional grid space is needed
- However, if we consider the input space, it's
O(nΒ²)
Overall space complexity: O(nΒ²)
(dominated by either the recursion stack or the BFS queue)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect BFS Level Processing
A critical pitfall is not properly processing BFS levels, which leads to incorrect distance calculation. Many implementations mistakenly increment the distance counter inside the neighbor-checking loop rather than after processing all nodes at the current level.
Incorrect approach:
while queue: row, col = queue.popleft() for dx, dy in directions: # Check neighbors... if grid[next_row][next_col] == 1: return distance distance += 1 # WRONG: Increments for each cell, not each level
Correct approach:
while queue:
# Process entire level before incrementing distance
for _ in range(len(queue)):
row, col = queue.popleft()
for dx, dy in directions:
# Check neighbors...
if grid[next_row][next_col] == 1:
return distance
distance += 1 # Increment after processing all cells at current distance
2. Not Marking Water Cells Before Adding to Queue
Failing to mark water cells as visited (value 2) immediately when adding them to the queue can cause duplicate processing and incorrect results.
Incorrect approach:
if grid[next_row][next_col] == 0: queue.append((next_row, next_col)) # Forgot to mark as visited!
Correct approach:
if grid[next_row][next_col] == 0: grid[next_row][next_col] = 2 # Mark immediately queue.append((next_row, next_col))
3. Using the Same Marker Value as Original Island
Using value 1 to mark visited cells would make it impossible to distinguish between the first island (already explored) and the second island (target).
Incorrect approach:
grid[row][col] = 1 # Can't distinguish from second island
Correct approach:
grid[row][col] = 2 # Use different value to mark first island and visited water
4. Starting BFS from a Single Cell Instead of All Island Cells
Starting BFS from only one cell of the first island rather than all boundary cells simultaneously would give incorrect distances.
Incorrect approach:
# Only add starting cell to queue queue.append((start_row, start_col)) # Then start BFS...
Correct approach:
def dfs(row, col):
queue.append((row, col)) # Add ALL island cells to queue
grid[row][col] = 2
# Continue DFS to add all cells...
5. Off-by-One Error in Distance Counting
The distance should represent the number of water cells to flip. A common mistake is returning distance + 1
or starting with distance = 1
.
Solution verification:
- When we find the second island, we've expanded
distance
times from the first island - The current
distance
value represents exactly how many water cells we crossed - No adjustment needed when returning the answer
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donβt Miss This!