Facebook Pixel

934. Shortest Bridge

Problem Description

You have an n x n binary matrix called grid where:

  • 1 represents land
  • 0 represents water

An island is defined as a group of 1s that are connected horizontally or vertically (4-directionally connected). These 1s are not connected to any other group of 1s outside the island.

The grid contains exactly two islands.

Your task is to connect these two islands into one single island by changing some 0s (water) to 1s (land). You want to find the minimum number of 0s that need to be changed to 1s 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 0s 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:

  1. Find and mark the first island (changing all its 1s to 2s)
  2. 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.

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

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:

  1. 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 1s to 2s). This way, we know exactly where one island is.

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

  3. 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...
  4. 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) where dirs = (-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 return ans as the minimum distance
    • If we find a cell with value 0 (water), we mark it as 2 (visited) and add it to the queue for the next level
    • Cells already marked as 2 are skipped to avoid revisiting
  • 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 ends
  • grid: 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 Evaluator

Example 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:

  1. 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Β²)
  2. 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:

  1. Recursion stack for DFS:

    • In the worst case, if the island forms a long path, the recursion depth could be O(nΒ²)
  2. BFS queue:

    • In the worst case, the queue could contain all cells of the first island
    • Maximum queue size: O(nΒ²)
  3. 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

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

Load More