6.26 Shortest Bridge
Source:
src/main/kotlin/grid/ShortestBridge.ktPattern: DFS sink + multi-source BFS · Core page
The Problem
The shortest bridge (number of 0s to flip) connecting two islands.
- Constraints: n ≤ 100.
Examples
Input: grid = [[0,1],[1,0]] -> Output: 1
Input: grid = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]] -> Output: 1
Intuition — label island 1 via DFS, expand with BFS until island 2
- DFS the first island, marking cells as
2and seeding a queue; - BFS from all island-2 cells through water until any
1is touched — the BFS depth is the bridge length.
fun dfs(x: Int, y: Int) {
if (x !in 0 until n || y !in 0 until n || grid[x][y] != 1) return
grid[x][y] = 2 // mark the first island
queue.add(x to y) // seed the BFS
directions.forEach { (dx, dy) -> dfs(x + dx, y + dy) }
}
// find the first 1, dfs it, then BFS the queue
var distance = 0
while (queue.isNotEmpty()) {
repeat(queue.size) {
val (x, y) = queue.removeFirst()
for ((dx, dy) in directions) {
val nx = x + dx; val ny = y + dy
if (nx !in 0 until n || ny !in 0 until n) continue
when (grid[nx][ny]) {
1 -> return distance // reached island 2!
0 -> { grid[nx][ny] = 2; queue.add(nx to ny) }
}
}
}
distance++
}
return -1
Why the DFS+queue combo? The DFS labels the whole first island (any start would do); the BFS then expands from every island cell simultaneously — the level-fenced distance IS the bridge length. The 6.17 sink + 6.14 multi-source BFS in one problem.
Why mark water as 2 during BFS? The visited-set-in-grid trick — expanded water can’t be re-expanded; island-1 cells (2) are skipped implicitly by the when.
Approach 1 — BFS between every island-1/2 pair (O(n⁴))
All-pairs distance: correct, slow.
Approach 2 — DFS label + multi-source BFS (the repo’s version, optimal)
class ShortestBridge {
/**
* @param grid 0/1 grid with two islands
* @return min water cells to flip
*/
fun shortestBridge(grid: Array<IntArray>): Int {
val directions = listOf(0 to 1, 1 to 0, 0 to -1, -1 to 0)
val queue = ArrayDeque<Pair<Int, Int>>()
val n = grid.size
fun dfs(x: Int, y: Int) {
if (x !in 0 until n || y !in 0 until n || grid[x][y] != 1) return
grid[x][y] = 2
queue.add(x to y)
directions.forEach { (dx, dy) -> dfs(x + dx, y + dy) }
}
outer@ for (i in 0 until n) for (j in 0 until n) {
if (grid[i][j] == 1) { dfs(i, j); break@outer }
}
var distance = 0
while (queue.isNotEmpty()) {
repeat(queue.size) {
val (x, y) = queue.removeFirst()
for ((dx, dy) in directions) {
val nx = x + dx
val ny = y + dy
if (nx !in 0 until n || ny !in 0 until n) continue
when (grid[nx][ny]) {
1 -> return distance
0 -> {
grid[nx][ny] = 2
queue.add(nx to ny)
}
}
}
}
distance++
}
return -1
}
}
import java.util.*;
public class ShortestBridge {
private int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private Queue<int[]> queue = new LinkedList<>();
private void dfs(int[][] grid, int x, int y) {
int n = grid.length;
if (x < 0 || y < 0 || x >= n || y >= n || grid[x][y] != 1) return;
grid[x][y] = 2;
queue.offer(new int[]{x, y});
for (int[] d : dirs) dfs(grid, x + d[0], y + d[1]);
}
/**
* @param grid 0/1 grid with two islands
* @return min water cells to flip
*/
public int shortestBridge(int[][] grid) {
int n = grid.length;
outer:
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (grid[i][j] == 1) { dfs(grid, i, j); break outer; }
int distance = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int s = 0; s < size; s++) {
int[] cell = queue.poll();
for (int[] d : dirs) {
int nx = cell[0] + d[0], ny = cell[1] + d[1];
if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
if (grid[nx][ny] == 1) return distance;
if (grid[nx][ny] == 0) {
grid[nx][ny] = 2;
queue.offer(new int[]{nx, ny});
}
}
}
distance++;
}
return -1;
}
}
#include <vector>
#include <queue>
class ShortestBridge {
int dirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
std::queue<std::pair<int, int>> queue;
void dfs(std::vector<std::vector<int>>& grid, int x, int y) {
int n = grid.size();
if (x < 0 || y < 0 || x >= n || y >= n || grid[x][y] != 1) return;
grid[x][y] = 2;
queue.push({x, y});
for (auto& d : dirs) dfs(grid, x + d[0], y + d[1]);
}
public:
/**
* @param grid 0/1 grid with two islands
* @return min water cells to flip
*/
int shortestBridge(std::vector<std::vector<int>>& grid) {
int n = grid.size();
bool found = false;
for (int i = 0; i < n && !found; i++)
for (int j = 0; j < n && !found; j++)
if (grid[i][j] == 1) { dfs(grid, i, j); found = true; }
int distance = 0;
while (!queue.empty()) {
int size = queue.size();
for (int s = 0; s < size; s++) {
auto [x, y] = queue.front(); queue.pop();
for (auto& d : dirs) {
int nx = x + d[0], ny = y + d[1];
if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
if (grid[nx][ny] == 1) return distance;
if (grid[nx][ny] == 0) {
grid[nx][ny] = 2;
queue.push({nx, ny});
}
}
}
distance++;
}
return -1;
}
};
from collections import deque
def shortest_bridge(grid: list[list[int]]) -> int:
"""
@param grid: 0/1 grid with two islands
@return: min water cells to flip
"""
n = len(grid)
dirs = ((0, 1), (1, 0), (0, -1), (-1, 0))
queue = deque()
def dfs(x, y):
if not (0 <= x < n and 0 <= y < n) or grid[x][y] != 1:
return
grid[x][y] = 2
queue.append((x, y))
for dx, dy in dirs:
dfs(x + dx, y + dy)
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
dfs(i, j)
break
else:
continue
break
distance = 0
while queue:
for _ in range(len(queue)):
x, y = queue.popleft()
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if not (0 <= nx < n and 0 <= ny < n):
continue
if grid[nx][ny] == 1:
return distance
if grid[nx][ny] == 0:
grid[nx][ny] = 2
queue.append((nx, ny))
distance += 1
return -1
#![allow(unused)]
fn main() {
use std::collections::VecDeque;
impl Solution {
/// @param grid 0/1 grid with two islands
/// @return min water cells to flip
pub fn shortest_bridge(grid: Vec<Vec<i32>>) -> i32 {
let n = grid.len();
let dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)];
let mut g = grid;
let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
fn dfs(g: &mut Vec<Vec<i32>>, x: i32, y: i32, queue: &mut VecDeque<(usize, usize)>) {
let n = g.len() as i32;
if x < 0 || y < 0 || x >= n || y >= n || g[x as usize][y as usize] != 1 { return; }
g[x as usize][y as usize] = 2;
queue.push_back((x as usize, y as usize));
for (dx, dy) in [(0, 1), (1, 0), (0, -1), (-1, 0)] {
dfs(g, x + dx, y + dy, queue);
}
}
'outer: for i in 0..n {
for j in 0..n {
if g[i][j] == 1 { dfs(&mut g, i as i32, j as i32, &mut queue); break 'outer; }
}
}
let mut distance = 0;
while !queue.is_empty() {
for _ in 0..queue.len() {
let (x, y) = queue.pop_front().unwrap();
for (dx, dy) in dirs {
let (nx, ny) = (x as i32 + dx, y as i32 + dy);
if nx < 0 || ny < 0 || nx >= n as i32 || ny >= n as i32 { continue; }
let (ux, uy) = (nx as usize, ny as usize);
if g[ux][uy] == 1 { return distance; }
if g[ux][uy] == 0 {
g[ux][uy] = 2;
queue.push_back((ux, uy));
}
}
}
distance += 1;
}
-1
}
}
}
Dry run
Input: grid = [[0,1],[1,0]].
dfs(0,1): mark 2. queue [(0,1)]. neighbors (1,1)=0 not 1; (0,0)=0 -> done.
BFS distance=0: pop (0,1). neighbors: (1,1)=0 -> mark 2, enqueue. (0,0)=0 -> mark, enqueue.
distance=1: pop (1,1): neighbor (1,0)=1 -> return 1 ✓
The level fence counts water rings: distance 0 is the island itself, distance 1 the first water ring, etc. The first 1 touched is island 2 — the ring number is the bridge length.
Complexity
Time. DFS + BFS each cell once:
$$ T(n) = O(n^2) $$
Space. The queue:
$$ S(n) = O(n^2) $$
Variants & follow-ups
- Max Area Of Island (6.17) — the DFS sink engine.
- Rotting Oranges (6.14) — the multi-source BFS fence.
- Interview follow-up: “Why is the bridge = the BFS ring number?” The first ring of water touching island 2 is exactly the water cells that separate the islands — flipping them connects the two. BFS’s level fencing measures that separation in minimum rings.