934. Shortest Bridge - Explanation

Problem Link

Description

You are given an n x n binary matrix grid where 1 represents land and 0 represents water.

An island is a 4-directionally connected group of 1's not connected to any other 1's. There are exactly two islands in grid.

You may change 0's to 1's to connect the two islands to form one island.

Return the minimum number of 0's you must flip to connect the two islands.

Example 1:

Input: grid = [
    [1,0],
    [0,1]
]

Output: 1

Example 2:

Input: grid = [
    [0,1,0],
    [0,0,0],
    [0,0,1]
]

Output: 2

Example 3:

Input: grid = [
    [1,1,1,1,1],
    [1,0,0,0,1],
    [0,1,1,0,1],
    [1,0,0,0,1],
    [1,1,1,1,1]
]

Output: 1

Constraints:

  • 2 <= grid.length == grid[i].length <= 100
  • grid[i][j] is either 0 or 1.
  • There are exactly two islands in grid.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Depth First Search (DFS) - Used to identify and mark all cells of one island before searching for the bridge
  • Breadth First Search (BFS) - Essential for finding the shortest path (minimum flips) between the two islands since BFS explores by levels
  • Graph Traversal on Grids - Understanding 4-directional movement and boundary checking in 2D matrices
  • Visited State Management - Tracking visited cells to avoid infinite loops, either with a separate data structure or by modifying the grid

1. Depth First Search + Breadth First Search - I

Intuition

The problem asks for the minimum number of 0s to flip to connect two islands. This is essentially finding the shortest path between the two islands across water.

First, we identify one island completely using DFS and mark all its cells as visited. Then, we use BFS to expand outward from this island. BFS naturally finds the shortest path because it explores all cells at distance 1 before distance 2, and so on. The moment we reach a cell belonging to the second island, we have found the minimum bridge length.

Algorithm

  1. Find the first land cell and run DFS to mark all cells of the first island as visited.
  2. Initialize a BFS queue with all cells from the first island.
  3. Perform BFS, expanding outward level by level:
    • For each cell, check all four neighbors.
    • If a neighbor is part of the second island (land and not visited), return the current distance.
    • If a neighbor is water and not visited, mark it visited and add to the queue.
  4. Increment the distance counter after processing each level.
  5. Return the distance when the second island is reached.
class Solution:
    def shortestBridge(self, grid: List[List[int]]) -> int:
        N = len(grid)
        direct = [[0, 1], [0, -1], [1, 0], [-1, 0]]

        def invalid(r, c):
            return r < 0 or c < 0 or r == N or c == N

        visit = set()

        def dfs(r, c):
            if invalid(r, c) or not grid[r][c] or (r, c) in visit:
                return
            visit.add((r, c))
            for dr, dc in direct:
                dfs(r + dr, c + dc)

        def bfs():
            res, q = 0, deque(visit)
            while q:
                for _ in range(len(q)):
                    r, c = q.popleft()
                    for dr, dc in direct:
                        curR, curC = r + dr, c + dc
                        if invalid(curR, curC) or (curR, curC) in visit:
                            continue
                        if grid[curR][curC]:
                            return res
                        q.append((curR, curC))
                        visit.add((curR, curC))
                res += 1

        for r in range(N):
            for c in range(N):
                if grid[r][c]:
                    dfs(r, c)
                    return bfs()

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n2)O(n ^ 2)

2. Depth First Search + Breadth First Search - II

Intuition

This is a space-optimized version of the previous approach. Instead of using a separate visited set, we modify the grid directly by marking visited land cells with the value 2. This distinguishes them from unvisited land (value 1) and water (value 0).

During BFS expansion, water cells are also marked as 2 when visited. When we encounter a cell with value 1, we know it belongs to the second island.

Algorithm

  1. Find the first land cell and run DFS to mark all its cells with value 2.
  2. During DFS, add each cell to the BFS queue.
  3. Perform BFS, expanding outward level by level:
    • For each cell, check all four neighbors.
    • If a neighbor has value 1, return the current distance (found second island).
    • If a neighbor has value 0, mark it as 2 and add to the queue.
  4. Increment the distance counter after processing each level.
  5. Return the distance when the second island is reached.
class Solution:
    def shortestBridge(self, grid: list[list[int]]) -> int:
        N, direct = len(grid), [(0, 1), (0, -1), (1, 0), (-1, 0)]

        def dfs(r, c):
            if 0 <= r < N and 0 <= c < N and grid[r][c] == 1:
                grid[r][c] = 2
                q.append((r, c))
                for dr, dc in direct:
                    dfs(r + dr, c + dc)

        q = deque()
        for r in range(N):
            for c in range(N):
                if grid[r][c]:
                    dfs(r, c)
                    break
            if q: break

        res = 0
        while q:
            for _ in range(len(q)):
                r, c = q.popleft()
                for dr, dc in direct:
                    nr, nc = r + dr, c + dc
                    if 0 <= nr < N and 0 <= nc < N:
                        if grid[nr][nc] == 1:
                            return res
                        if grid[nr][nc] == 0:
                            grid[nr][nc] = 2
                            q.append((nr, nc))
            res += 1

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n2)O(n ^ 2)

Intuition

We can use BFS for both phases: identifying the first island and expanding to find the bridge. This avoids the recursive overhead of DFS and may be preferable for very large islands.

The first BFS explores all connected land cells starting from the first land cell found, marking them as visited. The second BFS then expands from all boundary cells of the first island simultaneously, searching for the second island.

Algorithm

  1. Find the first land cell.
  2. Run BFS from this cell to identify all cells of the first island, marking them as 2 and adding them to a second queue.
  3. Perform BFS using the second queue, expanding outward level by level:
    • For each cell, check all four neighbors.
    • If a neighbor has value 1, return the current distance.
    • If a neighbor has value 0, mark it as 2 and add to the queue.
  4. Increment the distance counter after processing each level.
  5. Return the distance when the second island is reached.
class Solution:
    def shortestBridge(self, grid: list[list[int]]) -> int:
        N, direct = len(grid), [(0, 1), (0, -1), (1, 0), (-1, 0)]
        q2 = deque()

        found = False
        for r in range(N):
            if found: break
            for c in range(N):
                if grid[r][c] == 1:
                    q1 = deque([(r, c)])
                    grid[r][c] = 2
                    while q1:
                        x, y = q1.popleft()
                        q2.append((x, y))
                        for dx, dy in direct:
                            nx, ny = x + dx, y + dy
                            if 0 <= nx < N and 0 <= ny < N and grid[nx][ny] == 1:
                                grid[nx][ny] = 2
                                q1.append((nx, ny))
                    found = True
                    break

        res = 0
        while q2:
            for _ in range(len(q2)):
                x, y = q2.popleft()
                for dx, dy in direct:
                    nx, ny = x + dx, y + dy
                    if 0 <= nx < N and 0 <= ny < N:
                        if grid[nx][ny] == 1:
                            return res
                        if grid[nx][ny] == 0:
                            grid[nx][ny] = 2
                            q2.append((nx, ny))
            res += 1

        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n2)O(n ^ 2)

Intuition

A Disjoint Set Union (DSU) data structure can identify connected components. By scanning the grid and unioning adjacent land cells, we naturally group cells into their respective islands.

Once we know which cells belong to the first island (tracked during the initial union phase), we start BFS from the boundary cells of that island. As we expand, we union newly visited cells with the first island. When a union operation connects to a cell that was already land (value 1) but in a different component, we have found the bridge.

Algorithm

  1. Initialize a DSU with n * n + 1 elements.
  2. Scan the grid, unioning adjacent land cells. Track the first island's root.
  3. Identify boundary cells of the first island (cells adjacent to water) and add them to the BFS queue.
  4. Perform BFS, expanding outward level by level:
    • For each cell, check all four neighbors.
    • If a neighbor is land and unioning returns true (different component), return the current distance.
    • If a neighbor is water, mark it as land, union with current cell, and add to queue.
  5. Increment distance after each level.
  6. Return the distance when the islands are connected.
class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [1] * n

    def find(self, node):
        cur = node
        while cur != self.parent[cur]:
            self.parent[cur] = self.parent[self.parent[cur]]
            cur = self.parent[cur]
        return cur

    def union(self, u, v):
        pu, pv = self.find(u), self.find(v)
        if pu == pv:
            return False
        if self.rank[pv] > self.rank[pu]:
            pu, pv = pv, pu
        self.parent[pv] = pu
        self.rank[pu] += self.rank[pv]
        return True

class Solution:
    def shortestBridge(self, grid: list[list[int]]) -> int:
        n, direct = len(grid), [(0, 1), (0, -1), (1, 0), (-1, 0)]
        dsu = DSU(n * n + 1)

        def idx(r, c):
            return r * n + c + 1

        for r in range(n):
            for c in range(n):
                if grid[r][c] == 1:
                    first_island = dsu.find(idx(r, c))
                    if c + 1 < n and grid[r][c + 1] == 1:
                        dsu.union(idx(r, c), idx(r, c + 1))
                    if r + 1 < n and grid[r + 1][c] == 1:
                        dsu.union(idx(r, c), idx(r + 1, c))

        q = deque()
        for r in range(n):
            for c in range(n):
                if grid[r][c] == 1:
                    if dsu.find(idx(r, c)) != first_island:
                        continue
                    for dx, dy in direct:
                        nr, nc = r + dx, c + dy
                        if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
                            q.append((r,c))
                            break

        res = 0
        while q:
            for _ in range(len(q)):
                r, c = q.popleft()
                for dx, dy in direct:
                    nr, nc = r + dx, c + dy
                    if 0 <= nr < n and 0 <= nc < n:
                        if grid[nr][nc] == 1 and dsu.union(idx(r, c), idx(nr, nc)):
                            return res
                        if grid[nr][nc] == 0:
                            grid[nr][nc] = 1
                            dsu.union(idx(r, c), idx(nr, nc))
                            q.append((nr, nc))
            res += 1

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n2)O(n ^ 2)

Common Pitfalls

Not Marking Cells Before Adding to Queue

A common mistake is marking cells as visited only when dequeuing rather than when enqueuing. This causes the same cell to be added to the queue multiple times from different neighbors, leading to incorrect distance calculations and potential TLE. Always mark a cell as visited immediately when adding it to the BFS queue.

Confusing Island Detection with Bridge Finding

The DFS phase identifies all cells of the first island, while the BFS phase finds the shortest path to the second island. Mixing up these phases or using incorrect termination conditions (e.g., stopping DFS when finding any land cell) will produce wrong results. The BFS should return immediately upon reaching any cell with value 1 that was not part of the first island.

Incorrect Handling of the Return Value

When BFS reaches a cell of the second island, the current distance or steps counter represents the number of water cells flipped, which is the bridge length. Some implementations incorrectly return distance + 1 or forget that the starting cells of BFS (the first island's boundary) should not count toward the bridge length. The bridge length is the number of 0s between the islands, not including the island cells themselves.