当前:LC994 · 腐烂的橘子 · 首次出现于 Day 30 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC994 · Rotting Oranges · 网格 / BFS

腐烂的橘子:所有烂橘子按分钟同时扩散

每一分钟,已经腐烂的橘子同时把上下左右的新鲜橘子染烂。多源 BFS 的一层就是一分钟。

先把所有 2 入队,并数清新鲜橘子。每一轮只处理当前队列里的 size 个节点,代表同一分钟:它们把相邻的 1 改成 2、fresh 减一、入队。本轮确实传染出新的橘子,分钟加一。结束时 fresh>0 则返回 -1。时间 O(m·n),空间 O(m·n)。

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

网格里 0 是空格,1 是新鲜橘子,2 是腐烂橘子。每一分钟,所有已经腐烂的橘子同时把上下左右四格里的新鲜橘子变成腐烂。问:要多少分钟才能让所有橘子都腐烂?有新鲜橘子永远碰不到腐烂的,返回 -1。

主例 grid = [[2,1,1],[1,1,0],[0,1,1]]。一开始只有 (0,0) 是烂的。第 1 分钟 (0,1)、(1,0) 烂;第 2 分钟 (0,2)、(1,1) 烂;第 3 分钟 (2,1) 烂;第 4 分钟 (2,2) 烂。空格挡在 (1,2) 和 (2,0),(2,2) 必须等 (2,1) 先烂才能被传到,答案是 4。

本文要回答:为什么不能一个一个烂橘子轮流扩散,以及主例最后那一格为什么多等一分钟。

多源 BFS 按分钟扩散

传染是同时发生的:这一分钟所有烂橘子一起动手,不是先让 A 扩完再让 B 扩。单源 BFS 会把不同起点的时间轴错开。缺的是多源:第 0 分钟,所有已经腐烂的橘子站在同一层队头,它们的邻居属于第 1 分钟,再下一层是第 2 分钟。

先扫一遍网格。遇到 2 就入队,遇到 1 就 fresh++。然后进入 BFS。每一轮先记下当前队列长度 size,只弹出这 size 个——它们是同一分钟的传染源。每个源看四邻,邻格是 1 就改成 2,fresh 减一,入队(它们属于下一分钟)。这一轮如果确实往队尾追加了新橘子,分钟加一。用「追加过」而不是「无条件 +1」,是为了避免最后一轮空转多算一分钟。

用手走主例。队里一开始只有 (0,0),fresh 计的是其余新鲜橘子。第 1 分钟:(0,0) 把 (0,1)、(1,0) 染烂,这两格入队。第 2 分钟:它们再把 (0,2)、(1,1) 染烂。(1,2) 是 0,不是橘子;(2,0) 也是 0。第 3 分钟:(1,1) 把 (2,1) 染烂。(0,2) 的下方是 0,传不下去。第 4 分钟:(2,1) 右边是 (2,2),这才轮到最后一格。演示场景按分钟标出新烂的格子,最后一格单独再等一轮,答案 4。

若把 (2,1) 和 (2,2) 算在同一分钟,就当成了八向或当成了「看见邻居是 1 就连同它的邻居一起烂」。四向、一分钟一格,(2,2) 在第 3 分钟时四邻里还没有烂橘子,必须再等。

扫完之后 fresh 仍大于 0,说明有的 1 被 0 隔成了孤岛,返回 -1。一开始就没有 1,一分钟都不用,返回 0。全是 2,同样是 0。只有一个 1 紧贴一个 2,一分钟就够。每个格子从 1 变成 2 至多一次,时间和格子数成正比。

size 切片同一分钟的传染源在进这一轮时已经全部在队列里。先冻结 size,只处理这么多,新入队的属于下一分钟。层数就是分钟。
grid 3×3
2
1
1
1
1
0
0
1
1
分钟 0/剩余新鲜 5
分钟 0:烂橘子 (0,0)

Go:多源 BFS

solution.goGo
func orangesRotting(grid [][]int) int {
m, n := len(grid), len(grid[0])
q := [][2]int{}
fresh := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 2 { q = append(q, [2]int{i, j}) }
if grid[i][j] == 1 { fresh++ }
}
}
minutes := 0
dirs := [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
for len(q) > 0 {
size := len(q)
for k := 0; k < size; k++ {
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 && grid[ni][nj] == 1 {
grid[ni][nj] = 2
fresh--
q = append(q, [2]int{ni, nj})
}
}
}
if len(q) > 0 { minutes++ }
}
if fresh > 0 { return -1 }
return minutes
}

1所有 2 同时入队,fresh 记下 1 的个数。主例队头只有 (0,0)。

2size 冻结当前分钟的传染源。新染烂的橘子追加在队尾,下一轮才处理。

3len(q)>0 才 minutes++,避免最后一轮没有新传染还多加一分钟。主例加到 4。

4循环结束 fresh 仍大于 0,就是有橘子被空格隔开,返回 -1。

总结

所有烂橘子同时入队,一层一分钟。主例 (2,2) 等到第 4 分钟。

  • 同时传染 = 多源 BFS,不是轮流单源。
  • (2,2) 第 3 分钟四邻还没有烂橘子,必须再等 (2,1)。
  • 结束时还剩新鲜橘子,返回 -1;一开始就没有 1,返回 0。
同族题目
LC54201 矩阵LC1162地图分析LC200岛屿数量