LeetCode 934. 最短的桥
题目描述


题意分析
网格中恰好有两座四连通岛屿,需要把最少的水格
0变成陆地,使两座岛连在一起。答案只统计翻转的水格,原有陆地不需要付出代价。第一座岛内部本来就连通,从它的任何位置出发都不需要先翻水。因此应把整座岛作为起点集合,寻找它到另一座岛的最少水格距离。
解法:先标记第一座岛,再多源 BFS 拓展水域
核心思路
[!blue]
先找到任意一个陆地格,用显式栈遍历它所在的完整岛屿。每发现一个岛格就把它从
1改成2,并加入 BFS 队列。编号2表示已经发现;第一座岛全部标记后,网格中剩余的1就只能属于第二座岛。BFS 的所有初始格子都来自第一座岛,距离均为 0。设正在处理的这一层对应
steps次翻转:扩展到相邻水格时,需要再翻一个格子,所以它进入下一层;扩展到值为1的格子时已经到达第二座岛,陆地不用翻转,直接返回当前steps。队列按层处理,先搜索需要翻较少水格的位置,再搜索需要翻更多水格的位置。因此第一次碰到第二座岛,就已经找到了最少翻转数。水格在入队前立即改成
2,同一格只会由最早到达它的路径发现,不会重复入队。每层开始时必须固定当前层的格子数量,扩展中加入的水格属于下一层,不能立刻混在这一层处理。Go 的队列保留了已处理的前缀,因此当前层大小使用
len(queue) - head。
解题步骤
- 扫描网格,找到第一块陆地后,用显式栈标记整座岛,并把它的全部格子加入 BFS 队列;不再寻找其他起点。
- 将
steps初始化为 0,每轮先固定队列中尚未处理的当前层大小。- 取出这一层的每个格子,检查上下左右,跳过越界位置。
- 相邻格为
1时返回steps;为0时先改成2再入队;为2时跳过。- 当前层全部处理完后再令
steps++,继续扩展下一层水域。整个实现直接使用网格保存访问标记,会修改输入。
代码实现
class Solution {
private static final int[][] DIRS = {
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
};
public int shortestBridge(int[][] grid) {
int n = grid.length;
Queue<int[]> queue = new ArrayDeque<>();
boolean foundFirst = false;
for (int r = 0; r < n && !foundFirst; r++) {
for (int c = 0; c < n && !foundFirst; c++) {
if (grid[r][c] == 1) {
markFirstIsland(grid, r, c, queue);
foundFirst = true;
}
}
}
int steps = 0;
while (!queue.isEmpty()) {
// 先固定当前层,海水扩展的后继属于下一步。
int size = queue.size();
for (int i = 0; i < size; i++) {
int[] pos = queue.poll();
int r = pos[0];
int c = pos[1];
for (int[] d : DIRS) {
int nr = r + d[0];
int nc = c + d[1];
if (nr < 0 || nr >= n || nc < 0 || nc >= n) {
continue;
}
if (grid[nr][nc] == 1) {
// 第二岛陆地不需要翻转,返回已经经过的海水层数。
return steps;
}
if (grid[nr][nc] == 0) {
// 发现时立即标记,第一岛与海水扩展都不会重复加入同一格。
grid[nr][nc] = 2;
// 第一岛格子作为零距离起点;海水格则进入下一层。
queue.offer(new int[] {
nr,
nc
});
}
}
}
steps++;
}
return -1;
}
private void markFirstIsland(int[][] grid, int r, int c, Queue<int[]> queue) {
Deque<int[]> stack = new ArrayDeque<>();
grid[r][c] = 2;
stack.push(new int[] {
r,
c
});
queue.offer(new int[] {
r,
c
});
while (!stack.isEmpty()) {
int[] cell = stack.pop();
for (int[] d : DIRS) {
int nr = cell[0] + d[0];
int nc = cell[1] + d[1];
if (nr < 0
|| nr >= grid.length
|| nc < 0
|| nc >= grid[0].length
|| grid[nr][nc] != 1) {
continue;
}
// 发现时立即标记,第一岛与海水扩展都不会重复加入同一格。
grid[nr][nc] = 2;
stack.push(new int[] {
nr,
nc
});
// 第一岛格子作为零距离起点;海水格则进入下一层。
queue.offer(new int[] {
nr,
nc
});
}
}
}
}
func shortestBridge(grid [][]int) int {
n := len(grid)
queue := make([][2]int, 0)
dirs := [][2]int{
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
}
foundFirst := false
dfs := func(r, c int) {
grid[r][c] = 2
stack := [][2]int{
{r, c},
}
queue = append(queue, [2]int{
r,
c,
})
for len(stack) > 0 {
cell := stack[len(stack)-1]
stack = stack[:len(stack)-1]
for _, d := range dirs {
nr, nc := cell[0]+d[0], cell[1]+d[1]
if nr < 0 || nr >= n || nc < 0 || nc >= n || grid[nr][nc] != 1 {
continue
}
// 发现时立即标记,第一岛与海水扩展都不会重复加入同一格。
grid[nr][nc] = 2
stack = append(stack, [2]int{
nr,
nc,
})
// 第一岛格子作为零距离起点;海水格则进入下一层。
queue = append(queue, [2]int{
nr,
nc,
})
}
}
}
for i := 0; i < n && !foundFirst; i++ {
for j := 0; j < n && !foundFirst; j++ {
if grid[i][j] == 1 {
dfs(i, j)
foundFirst = true
}
}
}
steps := 0
head := 0
for head < len(queue) {
// 固定未处理的当前层,不把历史队列前缀算入。
size := len(queue) - head
for i := 0; i < size; i++ {
cur := queue[head]
head++
r, c := cur[0], cur[1]
for _, d := range dirs {
nr, nc := r+d[0], c+d[1]
if nr < 0 || nr >= n || nc < 0 || nc >= n {
continue
}
if grid[nr][nc] == 1 {
// 第二岛陆地不需要翻转,返回已经经过的海水层数。
return steps
}
if grid[nr][nc] == 0 {
// 发现时立即标记,第一岛与海水扩展都不会重复加入同一格。
grid[nr][nc] = 2
// 第一岛格子作为零距离起点;海水格则进入下一层。
queue = append(queue, [2]int{
nr,
nc,
})
}
}
}
steps++
}
return -1
}
复杂度分析
- 时间复杂度:$O(n^2)$。标记第一座岛和后续 BFS 都至多处理线性于网格总大小的格子,每个格子只检查四个方向。
- 空间复杂度:$O(n^2)$,用于标记岛屿的显式栈和 BFS 队列,没有额外的递归栈或访问数组。
关键点总结
[!green]
- 第一座岛的每个格子都是零代价起点,多源 BFS 同时从全部位置向外搜索。
- BFS 层数表示已翻转的水格数,到达第二座岛时不再增加代价。
- 入队前标记保证不重复扩展,按固定层大小处理保证距离含义不变。
易错点总结
[!yellow]
- 只从第一座岛的某一个格子开始 BFS:最近的连接位置可能在岛的另一侧,整座岛都应作为距离 0 的起点。
- 没有先完整标记第一座岛:搜索中遇到的
1可能仍属于第一座岛,不能据此判断已经到达第二座岛。- 遇到第二座岛时返回
steps + 1:会把不需要翻转的终点陆地也算进去。- 一边入队一边改变当前层大小:新加入的水格会被提前处理,导致一次循环跨过多层。
- 水格出队时才标记:它可能被同层的多个邻居重复加入队列,应在发现时立即标记。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 200. 岛屿数量 | 中等 | 先用连通遍历找到并标记第一座岛,才能把整座岛作为最短扩张的起点集合。 |
| 542. 01 矩阵 | 中等 | 同样多源BFS计算到目标集合的距离,本题从一座岛向外扩张直到碰到另一座岛。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!