最短的桥 图解题解
这道题到底在问什么
- 输入
- grid=[[0,1,0],[0,0,0],[0,0,1]]
- 输出
- 2
先想最直接的笨办法
轮到队首 (0,0)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。(动画第 10 步)
最优解:一步一步想明白
- 3记住「DFS 圈出第一座岛 → 多源 BFS 一圈圈扩水 → 撞到第二座岛时的层数就是答案」,下面逐帧套它。
- 4从左上往右下扫,第一个遇到的陆地 1 在 (0,0)。就从它出发,用 DFS 把它所在的整座岛找全。
- 5把 (0,0) 标记为第一座岛(绿),并放进 BFS 队列。接着沿四个方向继续 DFS,把同一座岛连着的 1 全找出来。
- 6DFS 走到 (0,1),它和第一座岛连着,也标成绿、入队。第一座岛现在有 2 格。
- 7DFS 走到 (1,1),它和第一座岛连着,也标成绿、入队。第一座岛现在有 3 格。
- 8第一座岛一共 3 格,全部进了队列(绿)。它们将同时作为 BFS 的多个起点,一起向外扩水。下面开始一圈圈淹。
- 9当前队列这 3 个格子离第一岛都是 0。看它们的邻居:是水就淹掉、新淹到的水离第一岛就是 1;若邻居是第二座岛的陆地,说明只填了 0 格水就接上了,答案就是 0。
- 10轮到队首 (0,0)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 11往下的 (1,0) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 12轮到队首 (0,1)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 13往右的 (0,2) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 14轮到队首 (1,1)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 15往下的 (2,1) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 16往右的 (1,2) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 17当前队列这 4 个格子离第一岛都是 1。看它们的邻居:是水就淹掉、新淹到的水离第一岛就是 2;若邻居是第二座岛的陆地,说明只填了 1 格水就接上了,答案就是 1。
- 18轮到队首 (1,0)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 19往下的 (2,0) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 20轮到队首 (0,2)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 21往右的 (0,3) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 22轮到队首 (2,1)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 23往下的 (3,1) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 24往右的 (2,2) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 25轮到队首 (1,2)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 26往右的 (1,3) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 27当前队列这 5 个格子离第一岛都是 2。看它们的邻居:是水就淹掉、新淹到的水离第一岛就是 3;若邻居是第二座岛的陆地,说明只填了 2 格水就接上了,答案就是 2。
- 28轮到队首 (2,0)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 29往下的 (3,0) 是水(0),把它淹掉、标成已访问(蓝),加入下一圈队列。注意这只是 BFS 的访问标记;最终最短桥用了几格,等接通第二岛时由层数定,不是所有淹过的水都算在桥里。
- 30轮到队首 (0,3)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 31轮到队首 (3,1)(紫),挨个看它的四个邻居:是水(0)就淹、是没标记的陆地就说明到了第二座岛。
- 32往右看 (3,2),它是一块还没标记的陆地,正是第二座岛!说明扩到第 2 圈就接上了,要填的水格数就是 2,这就是答案。
- 33回头看整趟:先一次 DFS 圈出第一座岛当多源起点,再用 BFS 一圈圈向外淹水,第一次淹到第二座岛时的圈数 2 就是最短桥。整个网格只走了常数遍,时间 O(n²)。
⚠️ 容易写错的地方
✗ 错:只圈一座岛却忘了「全部入队」
✓ 对:DFS 标第一座岛时每格都要进 BFS 队列
多源 BFS 要从第一座岛的整圈边界同时往外扩,只放一个点会算出偏大的距离
✗ 错:BFS 撞到第二岛时还 +1
✓ 对:撞到第二座岛 1 立刻返回当前 steps
答案是「填的水格数」;碰到对岸陆地那一步不需要填,所以返回的是进入这一层前累计的 steps
✗ 错:把第一座岛的格子重复当水填
✓ 对:标记成 2(或单独 visited),只填值为 0 的格子
不标记会把已属第一岛的格子反复入队,既错又可能死循环
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import deque
class Solution:
def shortestBridge(self, grid: List[List[int]]) -> int:
n = len(grid)
q = deque()
def dfs(r, c):
if r < 0 or r >= n or c < 0 or c >= n or grid[r][c] != 1:
return
grid[r][c] = 2
q.append((r, c))
dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1)
found = False
for r in range(n):
if found: break
for c in range(n):
if grid[r][c] == 1:
dfs(r, c); found = True; break
steps = 0
while q:
for _ in range(len(q)):
r, c = q.popleft()
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
nr, nc = r + dr, c + dc
if 0 <= nr < n and 0 <= nc < n:
if grid[nr][nc] == 1:
return steps
if grid[nr][nc] == 0:
grid[nr][nc] = 2
q.append((nr, nc))
steps += 1
return -1C++
#include <queue>
#include <vector>
using namespace std;
class Solution {
public:
int shortestBridge(vector<vector<int>>& grid) {
int n = grid.size();
queue<pair<int,int>> q;
function<void(int,int)> dfs = [&](int r, int c) {
if (r < 0 || r >= n || c < 0 || c >= n || grid[r][c] != 1) return;
grid[r][c] = 2; q.push({r, c});
dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1);
};
bool done = false;
for (int r = 0; r < n && !done; ++r) for (int c = 0; c < n; ++c) if (grid[r][c] == 1) { dfs(r, c); done = true; break; }
int dirs[5] = {1,0,-1,0,1};
for (int steps = 0; !q.empty(); ++steps) {
for (int sz = q.size(); sz--; ) {
auto [r, c] = q.front(); q.pop();
for (int d = 0; d < 4; ++d) {
int nr = r + dirs[d], nc = c + dirs[d+1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n) {
if (grid[nr][nc] == 1) return steps;
if (grid[nr][nc] == 0) { grid[nr][nc] = 2; q.push({nr, nc}); }
}
}
}
}
return -1;
}
};Java
import java.util.*;
class Solution {
int[][] grid;
int n;
Queue<int[]> q;
public int shortestBridge(int[][] grid) {
this.grid = grid; n = grid.length; q = new ArrayDeque<>();
boolean done = false;
for (int r = 0; r < n && !done; r++) for (int c = 0; c < n; c++) if (grid[r][c] == 1) { dfs(r, c); done = true; break; }
int[] dirs = {1,0,-1,0,1};
for (int steps = 0; !q.isEmpty(); steps++) {
for (int sz = q.size(); sz > 0; sz--) {
int[] cur = q.poll();
for (int d = 0; d < 4; d++) {
int nr = cur[0] + dirs[d], nc = cur[1] + dirs[d + 1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n) {
if (grid[nr][nc] == 1) return steps;
if (grid[nr][nc] == 0) { grid[nr][nc] = 2; q.offer(new int[]{nr, nc}); }
}
}
}
}
return -1;
}
private void dfs(int r, int c) {
if (r < 0 || r >= n || c < 0 || c >= n || grid[r][c] != 1) return;
grid[r][c] = 2; q.offer(new int[]{r, c});
dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1);
}
}复杂度
时间
O(n²)
DFS 标记第一座岛、BFS 扩水各自最多把每个格子访问常数次,网格共 n² 格,合起来 O(n²)
空间
O(n²)
队列最坏装下接近一整张网格的格子,加上 DFS 的递归栈,都是 O(n²) 级
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最短的桥 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么第一步用 DFS、第二步用 BFS,不统一?+
第一步只是要把第一座岛「圈完」、不关心顺序,DFS 写起来最短;也完全可以用 BFS 或并查集圈岛。第二步要的是「最短距离」,必须用 BFS 逐层扩散才能保证第一次到达即最短,DFS 在这里不保证最短。两步各取所长。
怎么保证图里恰好只处理两座岛?+
题目保证恰好两座岛。代码找到第一个 1 就 DFS 把它整座标记成 2,于是网格里剩下的 1 只属于第二座岛;BFS 扩水时一旦遇到值为 1 的格子,必然是第二座岛,直接返回即可,不会误判。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 最短的桥 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。