📖 第90课:腐烂的橘子

1 阅读19分钟

想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。

📖 第90课:腐烂的橘子

模块:图论 | 难度:Medium ⭐⭐ LeetCode 链接:leetcode.cn/problems/ro… 前置知识:第44课(BFS层序遍历)、第89课(岛屿数量) 预计学习时间:25分钟


🎯 题目描述

在一个 m × n 的网格中,每个格子可以是以下三个值之一:

  • 0 表示空格子
  • 1 表示新鲜橘子
  • 2 表示腐烂的橘子

每分钟,腐烂的橘子会使其上下左右四个方向的新鲜橘子腐烂。返回直到没有新鲜橘子为止所必须经过的最小分钟数。如果不可能使所有橘子腐烂,返回 -1

示例:

输入:grid = [
  [2,1,1],
  [1,1,0],
  [0,1,1]
]
输出:4
解释:
  初始:左上角橘子已腐烂
  第1分钟:腐烂扩散到(0,1)和(1,0)
  第2分钟:扩散到(0,2)和(1,1)和(2,1)
  第3分钟:扩散到(1,2)
  第4分钟:扩散到(2,2)
  全部腐烂,用时4分钟

约束条件:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10
  • grid[i][j] 的值为 012

🧪 边界用例(面试必考)

用例类型输入期望输出考察点
全是新鲜[[1,1],[1,1]]-1无腐烂源头,无法完成
已全腐烂[[2,2],[2,2]]0初始就完成,用时0
有隔离[[2,1,0,1]]-1空格子隔断,右侧橘子永远新鲜
多源同时扩散[[2,1,1],[0,1,1],[1,0,2]]2两个腐烂源同时扩散
无新鲜橘子[[0,2]]0没有新鲜橘子,返回0

💡 思路引导

生活化比喻

想象你在一个水果仓库,有几箱橘子已经开始腐烂...

🐌 笨办法:每分钟检查所有橘子,把腐烂橘子旁边的新鲜橘子标记为"下一分钟要腐烂",然后重复这个过程。这样需要反复遍历整个仓库,效率很低。

🚀 聪明办法:把所有"已腐烂的橘子"位置记录在一个清单上,每分钟只检查清单中的橘子,把它们周围的新鲜橘子加入"下一分钟清单"。这样每个橘子最多被检查一次,像波浪一样从所有腐烂源头同时向外扩散,直到覆盖所有能到达的橘子。

关键洞察

这是一个"多源BFS最短路径"问题:所有腐烂橘子同时向外扩散,每一层代表一分钟,层数就是所需时间


🧠 解题思维链

这一节模拟你在面试中"从零开始思考"的过程。

Step 1:理解题目 → 锁定输入输出

  • 输入:二维整数数组 grid,元素为 012
  • 输出:整数,表示所有新鲜橘子腐烂所需的最小分钟数;若不可能则返回 -1
  • 限制:腐烂是"同时扩散"的(所有腐烂橘子在同一分钟内各自向四周扩散)

Step 2:先想笨办法(暴力模拟)

每分钟遍历整个网格,找到所有腐烂橘子 2,将它们周围的新鲜橘子 1 改为 2,重复直到没有变化。

  • 时间复杂度:O(m × n × T),T 是总分钟数,最坏 O((m×n)²)
  • 瓶颈在哪:每分钟都要全网格扫描,大量重复检查

Step 3:瓶颈分析 → 优化方向

核心问题:"如何避免重复扫描已处理的格子?"

  • 关键:用队列记录"当前这一分钟要处理的腐烂橘子",下一分钟只处理新腐烂的橘子
  • 优化思路:多源BFS(Multi-Source BFS)—— 将所有初始腐烂橘子作为起点,同时开始BFS

Step 4:选择武器

  • 选用:BFS队列 + 分层遍历
  • 理由:
    • BFS天然按"层"扩展,每层代表一分钟
    • 多源BFS:初始时将所有腐烂橘子加入队列,之后逐层扩散
    • 时间复杂度优化到 O(m × n)

🔑 模式识别提示:当题目出现"同时扩散""最少步数""多个起点"等关键词,优先考虑 多源BFS 模式


🔑 解法一:多源BFS(标准解法)

思路

  1. 初始化:遍历网格,将所有腐烂橘子 2 的坐标加入队列,统计新鲜橘子 1 的数量
  2. BFS分层扩散:每次处理完队列中的一层(一分钟),时间 +1
  3. 检查结果:若还有新鲜橘子剩余,返回 -1;否则返回分钟数

图解过程

示例: [[2,1,1],[1,1,0],[0,1,1]]

初始化:
  queue = [(0,0)]  <- 初始腐烂橘子
  fresh_count = 6  <- 新鲜橘子数量
  minutes = 0

第1分钟:
  处理 (0,0),扩散到 (0,1) 和 (1,0)
  queue = [(0,1), (1,0)]
  fresh_count = 4
  minutes = 1

  网格状态:
  [2, 2, 1]
  [2, 1, 0]
  [0, 1, 1]

第2分钟:
  处理 (0,1) -> 扩散到 (0,2)
  处理 (1,0) -> 扩散到 (1,1)
  queue = [(0,2), (1,1), (2,1)]  <- (2,1)也被(1,1)扩散到
  fresh_count = 1
  minutes = 2

  网格状态:
  [2, 2, 2]
  [2, 2, 0]
  [0, 2, 1]

第3分钟:
  处理 (0,2) -> 无新扩散
  处理 (1,1) -> 扩散到 (1,2)
  处理 (2,1) -> 扩散到 (2,2)
  queue = [(1,2), (2,2)]
  fresh_count = 0  <- 注意:(1,2)和(2,2)在这一分钟都腐烂了,但还在队列中

实际上这里有个细节:第3分钟处理完后,队列还有元素,需要再处理一轮

修正:
第3分钟:
  处理3个元素后,queue = [(1,2)]
  minutes = 3

第4分钟:
  处理 (1,2) -> 扩散到 (2,2)
  queue = [(2,2)]
  minutes = 4

第5分钟:
  处理 (2,2) -> 无新扩散
  queue = []
  结束

实际上,正确的分层处理应该是:
第1分钟结束时,腐烂的是 (0,1) 和 (1,0),fresh_count = 4
第2分钟结束时,腐烂的是 (0,2)、(1,1)、(2,1),fresh_count = 1
第3分钟结束时,腐烂的是 (1,2),fresh_count = 0

等等,让我重新梳理:
初始:grid[0][0]=2,其余6个是1
第1分钟:(0,1)和(1,0)腐烂,剩余4个新鲜
第2分钟:(0,2)、(1,1)、(2,1)腐烂,剩余1个新鲜
第3分钟:(1,2)腐烂(从(1,1)和(2,1)同时扩散到),剩余0个新鲜

不对,题目示例说是4分钟。让我重新看:

实际路径:
(0,0)[初始] -> (0,1)[1分] -> (0,2)[2分]
                 (1,0)[1分] -> (1,1)[2分] -> (1,2)[3分] -> (2,2)[4分]
                                             (2,1)[2分] /

所以是4分钟正确。关键是 (2,2) 需要从 (1,2) 扩散,而 (1,2) 在第3分钟才腐烂。

重新整理:

初始:
  2 1 1
  1 1 0
  0 1 1

第0分钟结束(初始状态):queue = [(0,0)], fresh = 6

第1分钟结束:
  2 2 1
  2 1 0
  0 1 1
  从(0,0)扩散到(0,1)和(1,0),fresh = 4

第2分钟结束:
  2 2 2
  2 2 0
  0 2 1
  从(0,1)扩散到(0,2),从(1,0)扩散到(1,1),从(1,1)扩散到(2,1),fresh = 1

第3分钟结束:
  2 2 2
  2 2 2
  0 2 1
  从(1,1)扩散到(1,2),fresh = 1 (等等,(1,2)已腐烂,为什么还剩1个?)

重新数:初始新鲜橘子位置:
(0,1), (0,2), (1,0), (1,1), (2,1), (2,2) 共6个

第1分钟:(0,1)和(1,0)腐烂,剩余4个
第2分钟:(0,2), (1,1), (2,1)腐烂,剩余1个:(2,2)
第3分钟:(1,2)腐烂 (等等,没有(1,2)这个新鲜橘子,grid[1][2]=0是空格子!)

我搞错了!重新看网格:
  [2,1,1]  <- 坐标 (0,0)=2, (0,1)=1, (0,2)=1
  [1,1,0]  <- 坐标 (1,0)=1, (1,1)=1, (1,2)=0 (空!)
  [0,1,1]  <- 坐标 (2,0)=0, (2,1)=1, (2,2)=1

新鲜橘子:(0,1), (0,2), (1,0), (1,1), (2,1), (2,2) 共6个

扩散路径:
(0,0)[初始腐烂]
  -> 第1分钟:(0,1), (1,0) 腐烂
  -> 第2分钟:(0,2)[从0,1], (1,1)[从1,0], (2,1)[从1,0向下?不对,(1,0)下方是(2,0)=0]

重新分析相邻关系:
(0,0)的上下左右:(无上, 1,0下, 无左, 0,1右)
(0,1)的上下左右:(无上, 1,1下, 0,0左, 0,2右)
(1,0)的上下左右:(0,0上, 2,0下=0, 无左, 1,1右)
(1,1)的上下左右:(0,1上, 2,1下, 1,0左, 1,2右=0)
(2,1)的上下左右:(1,1上, 无下, 2,0左=0, 2,2右)

扩散:
初始:queue=[(0,0)], fresh=6
第1分钟:处理(0,0),扩散到(0,1)和(1,0),queue=[(0,1),(1,0)], fresh=4
第2分钟:处理(0,1),扩散到(0,2)和(1,1);处理(1,0),扩散到(1,1)(已在队列),queue=[(0,2),(1,1)], fresh=2
第3分钟:处理(0,2)无新扩散;处理(1,1),扩散到(2,1),queue=[(2,1)], fresh=1
第4分钟:处理(2,1),扩散到(2,2),queue=[(2,2)], fresh=0
结束,minutes=4

正确!

Python代码

from typing import List
from collections import deque


def orangesRotting(grid: List[List[int]]) -> int:
    """
    解法一:多源BFS
    思路:所有腐烂橘子作为起点,同时向外扩散,层数=分钟数
    """
    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh_count = 0

    # 初始化:收集所有腐烂橘子和新鲜橘子数量
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 2:
                queue.append((i, j))  # 腐烂橘子入队
            elif grid[i][j] == 1:
                fresh_count += 1      # 统计新鲜橘子

    # 边界情况:没有新鲜橘子
    if fresh_count == 0:
        return 0

    minutes = 0
    directions = [(-1,0), (1,0), (0,-1), (0,1)]  # 上下左右

    # BFS分层遍历
    while queue:
        # 处理当前层(当前这一分钟的所有腐烂橘子)
        size = len(queue)
        for _ in range(size):
            r, c = queue.popleft()
            # 向四个方向扩散
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                # 边界检查 + 新鲜橘子检查
                if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                    grid[nr][nc] = 2  # 标记为腐烂
                    fresh_count -= 1  # 新鲜橘子-1
                    queue.append((nr, nc))  # 下一分钟要处理

        # 本层处理完,时间+1(注意:只有队列非空才加时间)
        if queue:  # 关键:只有还有橘子要腐烂才计时
            minutes += 1

    # 检查是否还有新鲜橘子
    return minutes if fresh_count == 0 else -1


# ✅ 测试
print(orangesRotting([
    [2,1,1],
    [1,1,0],
    [0,1,1]
]))  # 期望输出:4

print(orangesRotting([[2,1,1],[0,1,1],[1,0,1]]))  # 期望输出:-1 (右下角孤立)
print(orangesRotting([[0,2]]))  # 期望输出:0 (无新鲜橘子)
print(orangesRotting([[1]]))    # 期望输出:-1 (无腐烂源头)

复杂度分析

  • 时间复杂度:O(m × n) — 每个格子最多入队一次
    • 初始化遍历:O(m × n)
    • BFS遍历:每个橘子最多入队一次,O(m × n)
    • 总计:O(m × n)
  • 空间复杂度:O(m × n) — 队列最坏情况下存储所有橘子
    • 极端情况:全是腐烂橘子,初始队列大小为 m × n

优缺点

  • ✅ 时间最优,每个格子只访问一次
  • ✅ 代码清晰,分层处理逻辑简单
  • ✅ 支持多源同时扩散
  • ⚠️ 修改了输入网格(可用 visited 数组避免)

🏆 解法二:原地标记 + 时间戳(最优解)

优化思路

解法一中,我们用队列的"分层"来区分不同分钟。实际上可以直接在网格中存储"腐烂时间",避免分层计数的复杂性。

💡 关键想法:用 grid[i][j] 存储这个橘子在第几分钟腐烂(初始腐烂=2,第1分钟=3,第2分钟=4...),最后返回 max(grid) - 2

图解过程

初始:
  2 1 1     <- 2表示第0分钟已腐烂
  1 1 0
  0 1 1

第1分钟:
  2 3 1     <- 3表示第1分钟腐烂
  3 1 0
  0 1 1

第2分钟:
  2 3 4     <- 4表示第2分钟腐烂
  3 4 0
  0 4 1

第3分钟:
  2 3 4
  3 4 0
  0 4 1     <- 注意:这里(2,2)=1无法被扩散到,因为周围没有新腐烂的

实际扩散应该是:
第1分钟:从2扩散,周围变为3
第2分钟:从3扩散,周围变为4
...

实际上这个优化并不明显,我们还是用标准的分层BFS更清晰。

Python代码

def orangesRotting_timestamp(grid: List[List[int]]) -> int:
    """
    解法二:时间戳标记(变体)
    思路:用数字表示腐烂时间,最后取最大值
    """
    rows, cols = len(grid), len(grid[0])
    queue = deque()

    # 收集初始腐烂橘子
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 2:
                queue.append((i, j, 0))  # (行,列,腐烂时间=0)

    max_time = 0
    directions = [(-1,0), (1,0), (0,-1), (0,1)]

    while queue:
        r, c, time = queue.popleft()
        max_time = max(max_time, time)

        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                grid[nr][nc] = 2  # 标记为腐烂
                queue.append((nr, nc, time + 1))  # 下一分钟腐烂

    # 检查是否还有新鲜橘子
    for row in grid:
        if 1 in row:
            return -1

    return max_time


# ✅ 测试
print(orangesRotting_timestamp([
    [2,1,1],
    [1,1,0],
    [0,1,1]
]))  # 期望输出:4

复杂度分析

  • 时间复杂度:O(m × n) — 相同
  • 空间复杂度:O(m × n) — 队列大小

🐍 Pythonic 写法

利用生成器表达式统计新鲜橘子:

def orangesRotting_pythonic(grid: List[List[int]]) -> int:
    """简洁版:用生成器表达式和any()简化边界检查"""
    from collections import deque

    rows, cols = len(grid), len(grid[0])
    queue = deque([(i, j) for i in range(rows) for j in range(cols) if grid[i][j] == 2])
    fresh = sum(row.count(1) for row in grid)

    if fresh == 0:
        return 0

    minutes = 0
    directions = [(-1,0), (1,0), (0,-1), (0,1)]

    while queue and fresh > 0:
        for _ in range(len(queue)):
            r, c = queue.popleft()
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                    grid[nr][nc] = 2
                    fresh -= 1
                    queue.append((nr, nc))
        minutes += 1

    return minutes - 1 if fresh == 0 else -1  # 注意:最后一轮多加了1

注意:上面的写法有个坑 — minutes 在最后一轮队列为空时还会 +1,所以需要 minutes - 1。实际面试中建议用标准版本,避免边界处理错误。

⚠️ 面试建议:先写清晰版本展示思路,再提 Pythonic 写法展示语言功底。 面试官更看重你的思考过程,而非代码行数。


📊 解法对比

维度🏆 解法一:多源BFS(最优)解法二:时间戳标记
时间复杂度O(m × n)O(m × n)
空间复杂度O(m × n)O(m × n)
代码难度简单简单
面试推荐⭐⭐⭐ ← 首选⭐⭐
适用场景通用,清晰代码稍简洁

为什么解法一是最优解:

  • 时间复杂度已达最优 O(m × n)(至少要遍历所有格子)
  • 分层逻辑清晰,用 size = len(queue) 明确区分每一分钟
  • 代码健壮,边界处理简单(队列为空自动结束)

面试建议:

  1. 先口述暴力法思路(每分钟全网格扫描),表明理解题意
  2. 立即优化到🏆最优解(多源BFS),强调"所有腐烂橘子同时扩散"
  3. 重点讲解:"初始时将所有腐烂橘子加入队列,BFS分层遍历,每层代表一分钟"
  4. 手动测试边界用例(无新鲜橘子/无腐烂源头/有隔离)
  5. 强调为什么是多源BFS而非单源:因为有多个初始腐烂橘子,它们要"同时"向外扩散

🎤 面试现场

模拟面试中的完整对话流程,帮你练习"边想边说"。

面试官:请你解决一下这道腐烂的橘子的题目。

:(审题30秒)好的,这道题要求计算所有新鲜橘子腐烂所需的最小分钟数,腐烂是同时向四周扩散的。让我先想一下...

我的第一个想法是每分钟遍历整个网格,找到所有腐烂橘子,将它们周围的新鲜橘子标记为腐烂,重复直到没有变化。但这样时间复杂度是 O((m×n)²),因为每分钟都要全网格扫描。

优化方法是用 多源BFS:初始时将所有腐烂橘子加入队列,然后逐层扩散,每层代表一分钟。这样时间复杂度优化到 O(m×n)。

面试官:很好,请写一下代码。

:(边写边说)我的步骤是:

  1. 初始化:遍历网格,收集所有腐烂橘子 2 的坐标,加入队列;同时统计新鲜橘子 1 的数量
  2. BFS分层遍历:每次处理完队列中的一层(用 size = len(queue) 记录本层元素数),时间 +1
  3. 向四个方向扩散:若相邻格子是新鲜橘子 1,标记为 2,新鲜计数 -1,加入队列
  4. 最后检查:若 fresh_count > 0,说明有橘子无法被腐烂,返回 -1;否则返回 minutes

关键点是用 size = len(queue) 明确区分每一分钟的扩散范围。

面试官:测试一下?

:用示例 [[2,1,1],[0,1,1],[1,0,1]] 走一遍:

  • 初始:队列 [(0,0)],新鲜橘子5个
  • 第1分钟:从 (0,0) 扩散到 (0,1),队列 [(0,1)],新鲜4个
  • 第2分钟:从 (0,1) 扩散到 (0,2)(1,1),队列 [(0,2), (1,1)],新鲜2个
  • 第3分钟:从 (1,1) 扩散到 (2,1),队列 [(2,1)],新鲜1个
  • 第4分钟:从 (2,1) 无法扩散到 (2,2)(中间隔了 (2,0)=0 空格子)
  • 队列为空,fresh_count = 1 > 0,返回 -1(正确,右下角橘子被隔离)

再测一个边界:全是新鲜橘子 [[1,1]],初始队列为空,直接返回 -1(正确)。

高频追问

追问应答策略
"如果腐烂速度不同呢?"可以在队列中存储 (r, c, speed),扩散时根据速度调整时间,用优先队列维护最早腐烂时间
"如果要返回具体的扩散路径?"在BFS时记录每个格子的前驱节点,最后回溯路径
"空间复杂度能优化到O(1)吗?"不能,BFS必须用队列存储待访问节点,最坏情况队列大小为 O(m×n)
"如果网格非常大怎么办?"可以只存储边界橘子坐标,而非所有腐烂橘子;或用并行BFS分块处理

🎓 知识点总结

Python技巧卡片 🐍

# 技巧1:列表推导式收集坐标
queue = deque([(i, j) for i in range(rows) for j in range(cols) if grid[i][j] == 2])

# 技巧2:sum() + 生成器统计元素
fresh_count = sum(row.count(1) for row in grid)

# 技巧3:BFS分层遍历模板
while queue:
    size = len(queue)  # 记录本层元素数
    for _ in range(size):
        node = queue.popleft()
        # 处理node...
    level += 1  # 本层处理完,层数+1

💡 底层原理(选读)

为什么用多源BFS而非多次单源BFS?

单源BFS:从每个腐烂橘子单独BFS,取最大时间 → 时间复杂度 O(k × m × n),k是腐烂橘子数

多源BFS:将所有腐烂橘子同时加入队列,一次BFS → 时间复杂度 O(m × n)

核心区别:多源BFS利用了"同时扩散"的特性,避免重复访问。

BFS分层的本质是什么?

BFS天然按"距离"分层,第 k 层的所有节点距离起点都是 k 步。

对于本题:

  • 第0层:初始腐烂橘子(距离=0分钟)
  • 第1层:第1分钟腐烂的橘子(距离初始腐烂橘子1步)
  • 第k层:第k分钟腐烂的橘子

size = len(queue) 记录每层元素数,确保按层处理。

算法模式卡片 📐

  • 模式名称:多源BFS(Multi-Source BFS)
  • 适用条件:多个起点同时向外扩散,求最短路径/最少步数/覆盖时间
  • 识别关键词:"同时扩散""多个起点""最少步数""感染传播"
  • 模板代码:
def multi_source_bfs(grid, sources):
    """多源BFS模板:从多个起点同时扩散"""
    from collections import deque

    queue = deque(sources)  # 初始:所有起点入队
    visited = set(sources)
    level = 0

    while queue:
        size = len(queue)
        for _ in range(size):
            node = queue.popleft()
            # 处理node...
            for neighbor in get_neighbors(node):
                if neighbor not in visited:
                    visited.add(neighbor)
                    queue.append(neighbor)
        level += 1

    return level

易错点 ⚠️

  1. 分层计时错误

    • ❌ 错误:每次 popleft()minutes += 1
    • ✅ 正确:处理完整层后才 minutes += 1,用 size = len(queue) 控制
  2. 忘记检查剩余新鲜橘子

    • ❌ 错误:直接返回 minutes,忽略孤立橘子
    • ✅ 正确:最后检查 fresh_count == 0,否则返回 -1
  3. 边界条件:无新鲜橘子

    • ❌ 错误:返回 minutes(可能是正数)
    • ✅ 正确:初始检查 if fresh_count == 0: return 0

🏗️ 工程实战(选读)

这个算法思想在真实项目中的应用,让你知道"学了有什么用"。

  • 场景1:病毒传播模拟:流行病学中模拟传染病扩散,多个初始感染者同时传播,计算多久能感染全部人群
  • 场景2:网络故障扩散:数据中心中某些服务器故障,故障会扩散到相邻服务器,计算多久整个集群瘫痪
  • 场景3:森林火灾模拟:多个起火点同时向周围扩散,计算整片森林烧毁所需时间
  • 场景4:游戏AI寻路:多个敌人同时向玩家移动,计算最快被包围的时间

🏋️ 举一反三

完成本课后,试试这些同类题目来巩固知识:

题目难度相关知识点提示
LeetCode 1162. 地图分析Medium多源BFS从所有陆地出发,求离陆地最远的海洋
LeetCode 542. 01矩阵Medium多源BFS从所有0出发,求每个1到最近0的距离
LeetCode 1091. 二进制矩阵最短路径MediumBFS最短路径从(0,0)到(n-1,n-1)的最短路径
LeetCode 1926. 迷宫中离入口最近的出口MediumBFS最短路径从入口到任意出口的最短步数
LeetCode 2258. 逃离火灾Hard多源BFS + 二分人和火同时扩散,求能到达安全屋的最晚出发时间

📝 课后小测

试试这道变体题,不要看答案,自己先想5分钟!

题目:如果腐烂橘子每分钟可以向八个方向(包含对角线)扩散,如何修改算法?时间复杂度会变化吗?

💡 提示(实在想不出来再点开)

只需修改方向数组,从4个方向改为8个方向,其他逻辑完全相同。

✅ 参考答案
def orangesRotting_8_directions(grid: List[List[int]]) -> int:
    """
    变体:八方向扩散
    思路:只需修改directions数组,其他不变
    """
    from collections import deque

    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh_count = 0

    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 2:
                queue.append((i, j))
            elif grid[i][j] == 1:
                fresh_count += 1

    if fresh_count == 0:
        return 0

    minutes = 0
    # 八个方向:上下左右 + 四个对角
    directions = [
        (-1,0), (1,0), (0,-1), (0,1),  # 上下左右
        (-1,-1), (-1,1), (1,-1), (1,1)  # 左上、右上、左下、右下
    ]

    while queue:
        size = len(queue)
        for _ in range(size):
            r, c = queue.popleft()
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                    grid[nr][nc] = 2
                    fresh_count -= 1
                    queue.append((nr, nc))
        if queue:
            minutes += 1

    return minutes if fresh_count == 0 else -1

时间复杂度:仍然是 O(m × n),因为每个格子最多入队一次,虽然方向数增加,但不影响渐进复杂度。

示例:

输入: [[2,1,1],
       [0,0,0],
       [1,1,1]]

四方向:永远无法腐烂下方橘子(中间隔了空行),返回-1
八方向:第1分钟从(0,0)对角扩散到(1,1),再扩散到下方,可以全部腐烂

如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。