1. 腐烂的橘子问题解析
这道题目是力扣(LeetCode)上经典的广度优先搜索(BFS)应用场景。题目描述一个网格中放置了新鲜橘子和腐烂橘子,每分钟腐烂橘子会使其上下左右相邻的新鲜橘子腐烂。我们需要计算所有橘子腐烂所需的最少时间,或者返回-1表示有橘子永远不会腐烂。
1.1 问题建模与算法选择
这个问题可以抽象为图论中的多源点最短路径问题。每个网格单元可以看作图中的一个节点,相邻网格单元之间存在边连接。腐烂橘子作为感染源,向四周扩散"感染"。
选择BFS算法的原因在于:
- 需要按层处理感染过程(每分钟对应一层扩散)
- 天然适合处理网格结构的相邻关系
- 可以同时处理多个源点的扩散过程
注意:虽然DFS也可以解决,但BFS更适合这类最短路径/最小时间问题,因为DFS可能会走"弯路"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 代码实现详解
2.1 核心算法流程
cpp复制class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
bool b=0;
int record=2;
while(1){
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]==record){
if(i+1<grid.size()&&grid[i+1][j]==1){grid[i+1][j]=record+1;b=1;}
if(i>0&&grid[i-1][j]==1){grid[i-1][j]=record+1;b=1;}
if(j+1<grid[0].size()&&grid[i][j+1]==1){grid[i][j+1]=record+1;b=1;}
if(j>0&&grid[i][j-1]==1){grid[i][j-1]=record+1;b=1;}
}
}
}
if(b==0) break;
else record++;
b=0;
}
for(int i=0;i<grid.size();i++)
for(int j=0;j<grid[0].size();j++)
if(grid[i][j]==1) b=1;
if(b==1) return -1;
return record-2;
}
};
2.2 关键变量说明
record:记录当前轮次(分钟数),从2开始递增b:标志位,判断本轮是否有新橘子被感染grid:网格状态,0表示空单元,1表示新鲜橘子,≥2表示腐烂橘子(数值代表腐烂的轮次)
2.3 感染过程解析
- 遍历整个网格,找到当前轮次的所有腐烂橘子(grid[i][j]==record)
- 检查其上下左右四个方向的新鲜橘子(grid[x][y]==1)
- 将相邻新鲜橘子标记为下一轮次(record+1)
- 设置标志位b表示本轮有新感染发生
- 如果没有新感染发生(b==0),终止循环
3. 算法优化与边界处理
3.1 时间复杂度分析
最坏情况下时间复杂度为O((mn)^2),其中m和n是网格的行列数。这是因为:
- 外层while循环最多执行mn次(每个格子最多感染一次)
- 内层双重循环每次都是O(mn)
实际运行中通常远小于这个上界,因为感染过程会快速收敛。
3.2 空间复杂度优化
当前实现是原地修改grid数组,空间复杂度为O(1)。另一种常见实现是使用队列:
- 初始将所有腐烂橘子坐标入队
- 每次处理完当前队列所有元素算作一轮
- 将新感染的橘子坐标加入队列
队列实现的时间复杂度为O(mn),但需要额外O(mn)空间。
3.3 边界条件处理
代码中已经考虑了网格边界:
i+1<grid.size()检查下方是否越界i>0检查上方是否越界j+1<grid[0].size()检查右侧是否越界j>0检查左侧是否越界
4. 常见问题与调试技巧
4.1 为什么初始record设为2?
因为:
- 原始腐烂橘子标记为2(第一轮)
- 下一轮新感染的橘子标记为3,以此类推
- 最终结果需要减去2得到实际分钟数
这样设计可以避免与空单元(0)和新鲜橘子(1)的标记冲突。
4.2 如何判断是否有橘子永远不会腐烂?
循环结束后再次遍历网格:
- 发现任何值为1的单元,说明有新鲜橘子未被感染
- 设置标志位b并返回-1
4.3 调试技巧
- 打印每轮结束后的网格状态:
cpp复制cout << "After minute " << record-2 << ":\n";
for(auto& row : grid){
for(int val : row) cout << val << " ";
cout << endl;
}
- 检查边界条件:
- 空网格([])
- 全新鲜橘子([[1,1],[1,1]])
- 全腐烂橘子([[2,2],[2,2]])
- 混合情况([[2,1,1],[1,1,0],[0,1,1]])
5. 算法变种与实际应用
5.1 多源点BFS的标准实现
更典型的BFS实现方式:
cpp复制int orangesRotting(vector<vector<int>>& grid) {
queue<pair<int,int>> q;
int fresh = 0;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]==2) q.push({i,j});
else if(grid[i][j]==1) fresh++;
}
}
if(fresh==0) return 0;
int dirs[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
int minutes = 0;
while(!q.empty()){
int size = q.size();
for(int i=0;i<size;i++){
auto [x,y] = q.front(); q.pop();
for(auto [dx,dy] : dirs){
int nx=x+dx, ny=y+dy;
if(nx>=0 && nx<grid.size() && ny>=0 && ny<grid[0].size() && grid[nx][ny]==1){
grid[nx][ny] = 2;
fresh--;
q.push({nx,ny});
}
}
}
if(!q.empty()) minutes++;
}
return fresh==0 ? minutes : -1;
}
5.2 实际应用场景
这类扩散模型可用于:
- 传染病传播模拟
- 森林火灾蔓延预测
- 网络信息传播分析
- 图像处理中的区域填充
在准备技术面试时,建议同时掌握原地修改和队列实现两种方式,并理解它们的时间/空间复杂度差异。这道题很好地考察了对BFS算法的理解程度和编码实现能力。
