LeetCode 542. 01 矩阵
题目描述


题意分析
对每个格子,求沿上下左右移动到任意一个零的最少步数。零到自身的距离是 $0$,题目保证至少有一个零。相邻格子间每次移动都花费一步,适合用 BFS 求最短距离。
解法:多源 BFS
核心思路
[!blue]
从每个位置寻找最近零,会重复搜索大量格子。由于相邻格子可以双向移动,可以反过来让所有零同时向外扩展:某格最先被哪个零到达,就得到到最近零的距离。
先把全部零设为距离 $0$ 并加入同一个队列,其余格子设为
-1,表示尚未确定。BFS 按距离从小到大处理节点;从距离为 $d$ 的格子发现未访问邻居时,将其距离写为 $d+1$,再入队。第一次发现就是最短距离:如果该邻居还存在更短路线,那么这条路线的前一个格子距离更小,早已出队并发现它,不可能到现在仍未访问。因此距离写入后无需再次更新,也无需区分它来自哪个零。
距离表同时充当访问标记,入队前就赋值,防止多个来源重复加入同一格。每格最多入队一次,所以两份同步保存行、列的队列只需预分配 $mn$ 个位置。题目没有不可通行的格子,且至少有一个零,搜索结束后所有距离都会确定。
解题步骤
- 完整遍历矩阵:零的距离设为 $0$ 并入队,一的距离设为
-1。- 取出队头坐标,枚举四个相邻位置。
- 跳过越界或距离不为
-1的邻居;其余邻居首次被发现,赋值为当前距离加一后入队。- 队列耗尽后返回距离矩阵。若全部格子都是零,每格已在初始化时得到答案,搜索不会修改这些距离。
代码实现
class Solution {
// 如果从每个 1 分别 BFS,复杂度会乘以 1 的数量,双重遍历很重。
public int[][] updateMatrix(int[][] mat) {
int m = mat.length;
int n = mat[0].length;
int[][] dist = new int[m][n];
int[] qx = new int[m * n];
int[] qy = new int[m * n];
int head = 0;
int tail = 0;
int[] dirs = new int[] {
-1,
0,
1,
0,
-1
};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 全部零同时作为距离零的起点
if (mat[i][j] == 0) {
qx[tail] = i;
qy[tail] = j;
tail++;
dist[i][j] = 0;
} else {
dist[i][j] = -1;
}
}
}
while (head < tail) {
int x = qx[head];
int y = qy[head];
head++;
for (int d = 0; d < 4; d++) {
int nx = x + dirs[d];
int ny = y + dirs[d + 1];
if (nx < 0 || ny < 0 || nx >= m || ny >= n || dist[nx][ny] != -1) {
continue;
}
// 第一次发现就写入最短距离,随后立即入队
dist[nx][ny] = dist[x][y] + 1;
qx[tail] = nx;
qy[tail] = ny;
tail++;
}
}
return dist;
}
}
func updateMatrix(mat [][]int) [][]int {
// 如果从每个 1 分别 BFS,复杂度会乘以 1 的数量,双重遍历很重。
m := len(mat)
n := len(mat[0])
dist := make([][]int, m)
qx := make([]int, m*n)
qy := make([]int, m*n)
head, tail := 0, 0
dirs := []int{
-1,
0,
1,
0,
-1,
}
for i := 0; i < m; i++ {
dist[i] = make([]int, n)
for j := 0; j < n; j++ {
// 全部零同时作为距离零的起点
if mat[i][j] == 0 {
dist[i][j] = 0
qx[tail] = i
qy[tail] = j
tail++
} else {
dist[i][j] = -1
}
}
}
for head < tail {
x := qx[head]
y := qy[head]
head++
for d := 0; d < 4; d++ {
nx := x + dirs[d]
ny := y + dirs[d+1]
if nx < 0 || ny < 0 || nx >= m || ny >= n {
continue
}
if dist[nx][ny] != -1 {
continue
}
// 第一次发现就写入最短距离,随后立即入队
dist[nx][ny] = dist[x][y] + 1
qx[tail] = nx
qy[tail] = ny
tail++
}
}
return dist
}
复杂度分析
- 时间复杂度:$O(mn)$,初始化扫描全部格子,搜索中每格最多出队一次并检查四个邻居。
- 空间复杂度:$O(mn)$,队列预分配格子总数大小,另有输出距离矩阵。
关键点总结
[!green]
- 所有零必须在向外扩展前完成初始化。
- 距离确定于入队时,而不是等待出队后才标记。
- 两份坐标队列同步读写,每格最多入队一次,所以容量 mn 足够。
易错点总结
[!yellow]
- 只从一个零开始,会把到其他零更近的位置算远。
- 未处理的一也初始化为零,会与已确定位置混淆。
- 读取邻居前不检查行列范围,会在边缘越界。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 994. 腐烂的橘子 | 中等 | 同样多源BFS,让所有初始源同时入队,层数表示到最近源的距离。 |
| 1162. 地图分析 | 中等 | 同样从所有陆地或目标格同时扩展,原题取各格最近距离中的最大值,本题返回完整距离矩阵。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!