当前:LC542 · 01 矩阵 · 首次出现于 Day 30 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC542 · 01 Matrix · 网格 / BFS

01 矩阵:从所有 0 同时往外扩

每个 1 要的是到最近 0 的步数。把全部 0 当作同一层起点做 BFS,谁先碰到这个 1,步数就是答案。

多源 BFS:所有 0 入队(距离已是 0),所有 1 先标成 -1 表示未访问。弹出一个格子,把它四邻里仍是 -1 的格子写成「当前距离 +1」并入队。第一次写入就是最短曼哈顿距离。时间 O(m·n),空间 O(m·n)。

时间 O(m·n)空间 O(m·n)结论先行 · 全文约 6 节
导读

给你一个只含 0 和 1 的矩阵,返回同样大小的矩阵:每个格子的值等于它到最近的 0 的曼哈顿距离。0 自己的距离是 0。只能走上下左右。

主例 mat = [[0,0,0],[0,1,1],[1,1,1]]。第一行和 (1,0) 都是 0。(1,1)、(1,2)、(2,0) 紧贴着 0,距离 1。(2,1) 最近的 0 要再走一步,距离 2。(2,2) 上方 (1,2) 已经是 1,再走一步也是 2。答案是 [[0,0,0],[0,1,1],[1,2,2]]。

对每个 1 单独 BFS 找 0,最坏每个格子都扫全图,平方级。本文要回答:为什么起点必须是全部 0,以及主例右下角为什么是 2 而不是绕左边走出来的 3。

所有 0 同时向外扩

一个 1 的答案,是它到最近那个 0 的步数。正着想,就要从每个 1 出发找 0,格子一多就重复劳动。反过来:距离的定义关于 0 是对称的——所有 0 都是「距离 0 的源」。缺的不是单源最短路,而是一次多源:让全部 0 同时往外走,谁先踩到某个 1,那一步数就是这个 1 的答案。

先扫一遍图。遇到 0,入队,距离保持 0。遇到 1,先改成 -1,表示还没被任何 0 的波前碰到。然后做普通 BFS:弹出队首,看它的四个邻居;邻居仍是 -1,就写成「当前格子的距离 +1」,并入队。-1 既是「这是原来的 1」,也是「尚未访问」。写成正数之后不会再被更新。

BFS 分层保证:第一次把 -1 改成正数时,用的就是最短路径。更远的 0 以后还会扩到这里,但那时格子已经不是 -1,会被直接跳过。所以不必在每个 1 上比较多个来源,队里的顺序已经比过了。

用手走主例。四个 0:(0,0)、(0,1)、(0,2)、(1,0) 同时在队里,距离都是 0。第一波走出的未访问邻居是 (1,1)、(1,2)、(2,0),都写成 1 并入队。(2,1) 和 (2,2) 这一轮还够不着。第二波从这三个距离 1 的格子再走: (2,1) 被 (1,1) 或 (2,0) 碰到,写成 2;(2,2) 被 (1,2) 碰到,写成 2。全图再无 -1。

(2,2) 若从左边 (2,0)→(2,1)→(2,2) 数,是 3,那是一条更长的路。多源 BFS 不允许它用 3 覆盖已经写下的 2:第一次访问来自上方的 (1,2),(1,2) 自己贴着顶行的 0,距离是 1,再加 1 就是 2。演示场景按层把波前推开,看的就是「哪一层第一次涂到这个 1」。

全是 0,队列一开始就装满,没有 -1 可更新,答案全 0。只有一个 1 被 0 包围,它会在第一波被写成 1。没有 0 的情况题目保证不会出现;若出现,-1 会留在图上,那是「不可达」,本题不需要处理。每个格子入队至多一次,时间和格子数成正比。

多源 BFS多个 0 当作同一层起点。BFS 的层数就是到最近源的距离。后到的源走得更远,写不进已经填过的格子,所以不必再取 min。
3×3
0
0
0
0
1
2
1
2
3
距离 0:所有 0

Go:多源 BFS

solution.goGo
func updateMatrix(mat [][]int) [][]int {
m, n := len(mat), len(mat[0])
q := [][2]int{}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if mat[i][j] == 0 { q = append(q, [2]int{i, j}) } else { mat[i][j] = -1 }
}
}
dirs := [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
for len(q) > 0 {
cur := q[0]; q = q[1:]
for _, d := range dirs {
ni, nj := cur[0]+d[0], cur[1]+d[1]
if ni >= 0 && ni < m && nj >= 0 && nj < n && mat[ni][nj] == -1 {
mat[ni][nj] = mat[cur[0]][cur[1]] + 1
q = append(q, [2]int{ni, nj})
}
}
}
return mat
}

10 全部入队当源。1 改成 -1,后面只对 -1 写入距离。

2从已经有距离的格子扩四邻。主例第一波写出三个 1,第二波写出两个 2。

3mat[ni][nj] == -1 保证每个 1 只被第一次碰到的源定价,也就是最近的 0。

4结果写回原矩阵。(2,2) 由 (1,2) 的 1 加一得到 2,不会写成绕行的 3。

总结

全部 0 同时 BFS,第一次涂到的层数就是到最近 0 的距离。主例右下角是 2。

  • 从每个 1 出发找 0 是平方级;反过来一次多源是线性。
  • 第一次访问即最短,后到的更远路径写不进去。
  • -1 兼任「原 1」和「未访问」。0 不必再更新。
同族题目
LC994腐烂的橘子LC1162地图分析LC417太平洋大西洋水流问题