Skip to content

生鲜电商最讨厌的算法题:腐烂的橘子

各位算法忍者们,欢迎来到忍者算法。今天要聊的这道题,让我想起了在生鲜电商实习时的一个场景...

🍊 从生鲜仓库说起

产品经理:"我们仓库里有一批橘子,已经发现有几个坏掉了。假设每天坏橘子会污染它周围的新鲜橘子,我们需要预估多少天后所有橘子会坏掉,这样才能及时调整库存..."

听起来是不是很像LeetCode 994题?

💡 问题的本质

这道题是这样描述的:

在一个 N × M 的网格中,每个单元格有三种可能的值:
- 0 表示空格子
- 1 表示新鲜橘子
- 2 表示腐烂的橘子

每分钟,腐烂的橘子会让上、下、左、右四个方向的新鲜橘子腐烂。
求需要多少分钟,整个网格中的橘子都会腐烂?如果不可能全部腐烂,返回 -1。

示例:
输入:[
    [2,1,1],
    [1,1,0],
    [0,1,1]
]
输出:4

🤔 第一反应可能是DFS?

很多同学看到网格搜索就想到DFS(深度优先搜索)。但等等,这题有个关键词:时间

想象一下腐烂过程:

  • 第0分钟:初始状态,有些橘子已经腐烂
  • 第1分钟:这些腐烂橘子同时感染周围的橘子
  • 第2分钟:新腐烂的橘子又同时感染周围的...

这不就是**广度优先搜索(BFS)**的经典场景吗?

⚡ 代码实现:BFS的完美运用

java
class Solution {
    public int orangesRotting(int[][] grid) {
        int rows = grid.length, cols = grid[0].length;
        Queue<int[]> queue = new LinkedList<>();
        int freshCount = 0;  // 新鲜橘子计数
        
        // 第一步:找到所有腐烂的橘子,入队
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (grid[i][j] == 2) {
                    queue.offer(new int[]{i, j});
                } else if (grid[i][j] == 1) {
                    freshCount++;
                }
            }
        }
        
        // 如果没有新鲜橘子,直接返回0
        if (freshCount == 0) return 0;
        
        int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int minutes = 0;
        
        // 开始BFS
        while (!queue.isEmpty() && freshCount > 0) {
            minutes++;  // 时间+1
            int size = queue.size();  // 当前层的腐烂橘子数
            
            // 处理当前层的每个腐烂橘子
            for (int i = 0; i < size; i++) {
                int[] pos = queue.poll();
                
                // 向四个方向扩散
                for (int[] dir : directions) {
                    int newRow = pos[0] + dir[0];
                    int newCol = pos[1] + dir[1];
                    
                    // 检查边界和是否是新鲜橘子
                    if (newRow >= 0 && newRow < rows && 
                        newCol >= 0 && newCol < cols && 
                        grid[newRow][newCol] == 1) {
                        
                        grid[newRow][newCol] = 2;  // 感染这个橘子
                        queue.offer(new int[]{newRow, newCol});
                        freshCount--;  // 新鲜橘子-1
                    }
                }
            }
        }
        
        // 如果还有新鲜橘子,返回-1
        return freshCount == 0 ? minutes : -1;
    }
}

🎯 解题关键点

就像处理仓库里的水果:

  1. 先统计现状:

    • 有多少新鲜橘子
    • 哪些位置已经腐烂
  2. 模拟腐烂过程:

    • 每分钟所有腐烂橘子同时发挥"作用"
    • 用队列记录每一轮新腐烂的橘子
  3. 终止条件:

    • 要么所有橘子都腐烂(返回时间)
    • 要么有橘子永远不会腐烂(返回-1)

📊 复杂度分析

时间复杂度:O(M × N)

  • M和N是网格的行数和列数
  • 每个格子最多被访问一次

空间复杂度:O(M × N)

  • 最坏情况:所有橘子都腐烂
  • 队列可能需要存储所有格子的坐标

🎯 面试官最爱追问

  1. Q:如何优化空间复杂度? A:实际上这是最优解了,因为我们必须追踪每个腐烂的橘子

  2. Q:如果橘子是3D放置的呢? A:只需将方向数组改为六个方向(上下左右前后)

  3. Q:如何输出每个橘子腐烂的具体时间? A:可以用一个额外的二维数组记录时间戳

💡 举一反三

这个BFS模板还可以用在:

  • 迷宫最短路径问题
  • 僵尸感染问题
  • 单词演变问题
  • 细胞扩散问题

🎁 思考题

如果有些格子是"隔离区"(值为3),腐烂不能透过它传播,如何修改代码?

例如:
2 1 1    这里的3像墙一样
1 3 1    阻止腐烂传播
0 1 1

如果你知道答案?欢迎在评论区留言~

📝 面试技巧

回答这题时,建议这样组织语言:

  1. 先说明为什么选择BFS(时间维度的特点)
  2. 解释统计新鲜橘子的必要性
  3. 强调队列size的作用(区分不同时间层)

作者:忍者算法
公众号:忍者算法

🎯 回复【刷题清单】获取更多BFS经典题目解析 👥 回复【加群】加入算法/面试交流群,一起学习进步 🧑‍💻 回复【代码】获取GitHub完整题解项目,包含Java/Python/JavaScript/Go/C++多语言实现

#算法面试 #LeetCode #BFS #面试高频题