力扣994题解析:多源BFS解决腐烂橘子问题
2026/8/9 19:44:45 网站建设 项目流程

1. 问题背景与题目解析

今天我们来聊聊力扣(LeetCode)上一道经典的广度优先搜索(BFS)题目——"腐烂的橘子"(题目编号994)。这道题在力扣的hot100题库中热度很高,也是面试中经常出现的算法题之一。

题目描述是这样的:在一个给定的网格中,每个单元格可以有以下三个值之一:

  • 0 代表空单元格
  • 1 代表新鲜橘子
  • 2 代表腐烂的橘子

每分钟,任何与腐烂橘子相邻(上下左右)的新鲜橘子都会腐烂。我们需要计算直到没有橘子可以继续腐烂时所需的最小分钟数。如果不可能使所有橘子都腐烂,则返回-1。

举个例子: 输入: [[2,1,1], [1,1,0], [0,1,1]] 输出:4

这个题目看似简单,但考察了多个重要的算法概念和编程技巧。接下来,我将从多个角度深入分析这道题的解法。

2. 解题思路分析

2.1 问题建模

首先,我们需要将这个问题转化为计算机可以处理的形式。这实际上是一个典型的图论问题:

  • 每个橘子(值为1或2的单元格)可以看作图中的一个节点
  • 相邻的橘子之间存在边(上下左右四个方向)
  • 腐烂过程就是从初始腐烂节点开始的广度优先遍历

2.2 关键观察点

解决这个问题的关键在于几个重要观察:

  1. 腐烂过程是同步进行的:所有当前腐烂的橘子会同时影响它们周围的新鲜橘子
  2. 我们需要跟踪"轮次":每一轮代表一分钟的时间流逝
  3. 最终需要检查是否还有新鲜橘子剩余

2.3 算法选择

基于上述观察,广度优先搜索(BFS)是最合适的选择,原因如下:

  • BFS天然适合处理"层级"或"轮次"的概念
  • 它可以同时从多个起点开始搜索(多源BFS)
  • 能够保证找到最短时间(最小轮次)

3. 详细实现步骤

3.1 初始准备

首先,我们需要准备以下数据:

  1. 记录所有初始腐烂橘子的位置(队列初始化)
  2. 统计新鲜橘子的数量(用于最终判断)
  3. 定义四个方向的移动向量(上、下、左、右)
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

3.2 BFS框架搭建

标准的BFS框架包括:

  1. 初始化队列
  2. 记录访问状态(本题中可以通过直接修改网格值来实现)
  3. 层级/轮次计数
from collections import deque def orangesRotting(grid): queue = deque() fresh = 0 rows, cols = len(grid), len(grid[0]) # 初始化:找到所有腐烂橘子和新鲜橘子数量 for r in range(rows): for c in range(cols): if grid[r][c] == 2: queue.append((r, c)) elif grid[r][c] == 1: fresh += 1

3.3 多源BFS实现

多源BFS的关键在于:

  1. 在每一轮开始时,记录当前队列的大小
  2. 处理完这一批所有节点后,再处理新加入的节点
  3. 只有处理完一整轮,才增加时间计数
time = 0 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)) if queue: # 只有有新腐烂的橘子才增加时间 time += 1 return time if fresh == 0 else -1

4. 复杂度分析与优化

4.1 时间复杂度

  • 我们需要遍历整个网格两次:
    • 第一次初始化(统计新鲜橘子和腐烂橘子)
    • 第二次BFS过程
  • 最坏情况下,所有橘子都会腐烂,时间复杂度为O(m×n),其中m和n是网格的行数和列数

4.2 空间复杂度

  • 队列的大小最多为O(m×n)(当所有橘子同时腐烂时)
  • 因此空间复杂度也是O(m×n)

4.3 可能的优化

  1. 提前终止:如果在某一轮结束后新鲜橘子数量已经为0,可以提前退出
  2. 并行处理:对于特别大的网格,可以考虑并行处理不同区域的腐烂过程
  3. 空间优化:可以使用位运算或其他技巧减少空间使用,但对于这个问题意义不大

5. 边界条件与测试用例

5.1 常见边界情况

  1. 网格为空:应该返回0(因为没有橘子需要腐烂)
  2. 没有新鲜橘子:直接返回0
  3. 没有腐烂橘子:
    • 如果有新鲜橘子:返回-1
    • 如果没有新鲜橘子:返回0
  4. 新鲜橘子无法被全部腐烂:返回-1

5.2 测试用例设计

好的测试用例应该包括:

  1. 常规情况:
    [[2,1,1],[1,1,0],[0,1,1]] # 预期输出:4
  2. 无法全部腐烂:
    [[2,1,1],[0,1,1],[1,0,1]] # 预期输出:-1
  3. 没有新鲜橘子:
    [[0,2]] # 预期输出:0
  4. 多个腐烂源:
    [[2,1,1],[2,1,1],[1,1,2]] # 预期输出:2

6. 实际编码中的注意事项

6.1 常见错误

  1. 时间计数错误:
    • 忘记在队列不为空时才增加时间
    • 在每处理一个橘子后就增加时间
  2. 新鲜橘子计数错误:
    • 没有正确初始化fresh计数器
    • 在腐烂橘子时没有减少fresh计数
  3. 边界检查不完整:
    • 没有检查网格索引是否越界
    • 没有处理空网格的情况

6.2 调试技巧

  1. 打印中间状态:
    print(f"Time: {time}, Fresh: {fresh}, Queue size: {len(queue)}")
  2. 可视化网格变化:
    for row in grid: print(row) print()
  3. 使用小网格测试边界条件

7. 算法扩展与变种

7.1 相关题目

  1. 墙与门(LeetCode 286):类似的多源BFS问题
  2. 岛屿数量(LeetCode 200):连通分量问题
  3. 01矩阵(LeetCode 542):多源BFS的另一个应用

7.2 变种问题

  1. 橘子腐烂速度不同:某些橘子腐烂速度更快或更慢
  2. 三维空间中的橘子腐烂:网格变为三维
  3. 橘子有抗腐烂能力:需要多次接触才会腐烂
  4. 动态添加新鲜橘子:在腐烂过程中不断有新鲜橘子加入

8. 面试中的应用

8.1 面试官考察点

  1. 对BFS算法的理解和应用能力
  2. 处理多源BFS的能力
  3. 边界条件的考虑
  4. 代码的整洁度和可读性
  5. 时间/空间复杂度分析能力

8.2 回答策略

  1. 先明确问题并给出简单例子
  2. 讨论可能的算法选择(为什么选BFS而不是DFS)
  3. 逐步构建解决方案
  4. 讨论时间/空间复杂度
  5. 提出可能的优化
  6. 考虑边界条件

8.3 常见面试问题

  1. 如何证明你的算法能找到最小时间?
    • BFS保证最短路径/最小轮次
  2. 如果网格非常大怎么办?
    • 讨论并行处理或分布式算法
  3. 如何修改算法处理腐烂速度不同的情况?
    • 引入优先级队列或不同的处理逻辑

9. 个人实战经验分享

在实际解决这个问题时,我遇到了几个有趣的坑:

  1. 时间计数问题:最初我在每个橘子处理后都增加时间,导致结果偏大。正确的做法是在处理完一整轮所有当前腐烂橘子后才增加时间。

  2. 新鲜橘子计数:忘记在初始化时统计新鲜橘子数量,导致无法正确判断是否所有橘子都已腐烂。

  3. 多源BFS的队列初始化:一开始只加入了一个腐烂橘子,而忽略了其他同时存在的腐烂源。

一个实用的调试技巧是可视化每一分钟后的网格状态,这能帮助快速定位问题所在。例如:

初始状态: [2,1,1] [1,1,0] [0,1,1]

第1分钟后: [2,2,1] [2,1,0] [0,1,1]

第2分钟后: [2,2,2] [2,2,0] [0,1,1]

...

通过这种可视化,可以清晰看到腐烂过程的进展,帮助验证算法的正确性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询