1. 题目背景与核心思路
这道LeetCode 994题"腐烂的橘子"是一个经典的网格遍历问题。题目描述了一个m×n的网格,每个格子可能是以下三种状态之一:
- 0代表空单元格
- 1代表新鲜橘子
- 2代表腐烂的橘子
每分钟,任何与腐烂橘子相邻(上下左右)的新鲜橘子都会腐烂。我们需要计算直到没有新鲜橘子可以被腐烂为止所需的最小分钟数,如果不可能则返回-1。
这个问题的核心在于理解多源BFS(广度优先搜索)的应用场景。与单源BFS不同,多源BFS需要同时从多个起点开始扩展,这在处理类似传染病扩散、多点火源蔓延等场景时特别有用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 多源BFS算法解析
2.1 算法基本框架
多源BFS的实现关键在于初始队列的构建。我们需要:
- 遍历整个网格,将所有腐烂橘子的位置加入队列
- 记录初始新鲜橘子的数量
- 使用一个特殊标记(如-1)来区分不同"层"的BFS遍历
cpp复制int orangesRotting(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
queue<pair<int, int>> q;
int fresh = 0;
// 初始化队列和新鲜橘子计数
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(grid[i][j] == 2) {
q.push({i, j});
} else if(grid[i][j] == 1) {
fresh++;
}
}
}
// 如果没有新鲜橘子,直接返回0
if(fresh == 0) return 0;
// BFS主循环
int minutes = -1;
vector<pair<int, int>> dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while(!q.empty()) {
int size = q.size();
minutes++;
for(int i = 0; i < size; i++) {
auto [x, y] = q.front();
q.pop();
for(auto [dx, dy] : dirs) {
int nx = x + dx;
int ny = y + dy;
if(nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
grid[nx][ny] = 2;
fresh--;
q.push({nx, ny});
}
}
}
}
return fresh == 0 ? minutes : -1;
}
2.2 时间复杂度分析
该算法的时间复杂度为O(m×n),其中m和n分别是网格的行数和列数。这是因为:
- 初始遍历整个网格来构建队列和计数:O(m×n)
- BFS过程中每个格子最多被访问一次:O(m×n)
- 每个格子的四个邻居检查:O(4) = O(1)
因此总时间复杂度为O(m×n),这是最优解。
3. 关键实现细节与优化
3.1 层数统计技巧
在标准BFS中,我们通常使用一个额外的变量来记录当前遍历的层数。在这个问题中,每分钟对应BFS的一层。实现上有两种常见方式:
- 使用特殊标记:在每层结束时插入一个特殊标记(如nullptr或(-1,-1)),遇到标记时增加分钟计数
- 预先记录队列大小:在每层开始前记录当前队列大小,然后处理正好这么多元素(如上面代码所示)
第二种方法更为简洁高效,避免了不必要的标记处理。
3.2 新鲜橘子计数优化
在初始遍历时记录新鲜橘子的数量,然后在BFS过程中每当一个新鲜橘子腐烂时就减少计数。这样可以在BFS结束时直接通过检查该计数是否为0来判断是否所有橘子都能被腐烂,而不需要再次遍历整个网格。
3.3 方向数组的使用
使用方向数组(dirs)来表示四个移动方向,可以使代码更简洁且易于修改。如果需要考虑对角线方向的传播(虽然本题不需要),只需简单扩展这个数组即可。
4. 边界条件与特殊情况处理
4.1 无新鲜橘子的情况
如果初始网格中没有新鲜橘子(fresh == 0),应该直接返回0,因为不需要任何时间就能达到目标状态。
4.2 无法全部腐烂的情况
在BFS结束后,如果仍有新鲜橘子剩余(fresh > 0),说明存在无法被腐烂的橘子,应该返回-1。这种情况通常发生在新鲜橘子被空单元格隔离时。
4.3 网格为空的情况
虽然题目保证网格不为空,但在实际编程中应该检查grid.empty()或grid[0].empty()的情况,避免潜在的错误。
5. 算法扩展与变种思考
5.1 多源BFS的其他应用场景
多源BFS还可应用于:
- 火灾蔓延模拟
- 病毒传播模型
- 多目标最短路径问题
- 图像处理中的区域填充
5.2 问题变种思考
如果题目改为橘子腐烂需要不同的时间(比如不同类型橘子腐烂速度不同),我们可以修改算法为优先级队列(Dijkstra算法)的实现。
如果考虑橘子腐烂的概率性,可以引入随机因素和蒙特卡洛模拟。
6. 常见错误与调试技巧
6.1 忘记初始化分钟计数
minutes初始值应该设为-1而非0,因为:
- 第一次循环处理的是初始腐烂的橘子,这不算作一分钟
- 之后每分钟对应一次完整的队列处理
6.2 边界检查不完整
在检查相邻格子时,必须确保新坐标(nx, ny)在网格范围内,否则会导致数组越界错误。
6.3 状态更新不及时
在将新鲜橘子标记为腐烂后,必须立即将其加入队列并减少新鲜橘子计数,否则可能导致重复处理或计数错误。
7. 性能优化进阶
7.1 并行BFS
对于极大网格,可以考虑并行化BFS处理。将网格分区,每个处理器负责一个区域,在边界处进行同步。
7.2 多源BFS与A*结合
在某些变种问题中,如果需要寻找特定目标,可以结合启发式搜索来优化路径。
7.3 位图优化
对于极大但稀疏的网格,可以使用位图来表示橘子状态,减少内存使用和提高缓存命中率。
8. 实际工程应用启示
虽然这是一个算法题,但其核心思想在实际工程中有广泛应用:
- 网络广播协议:多源BFS类似于网络中的广播风暴控制
- 游戏开发:用于实现迷雾探索、单位视野等系统
- GIS系统:计算多个地点的影响范围
- 疫情防控:模拟病毒传播路径和速度
理解这类基础算法问题有助于我们在面对复杂工程问题时快速识别模式并选择合适解决方案。
