LC 994 腐烂的橘子:人人都说是 BFS 入门题,我却写了三遍才过

刷到这道题的时候我心里还窃喜。这不就是典型的扩散题吗?BFS 直接秒了啊。结果啪啪打脸,连错两次,第三次才堪堪通过。说出来你们可能不信,错的地方全是我觉得 "根本不可能错" 的小细节。

题目一句话说清

给你一个 m x n 的网格,每个格子有三种状态:0 是空位,1 是新鲜橘子,2 是烂橘子。

每过一分钟,所有烂橘子都会把上下左右四个相邻的新鲜橘子也传染烂。问你最少需要多少分钟,能让所有橘子都烂掉。如果总有新鲜橘子躲在角落烂不了,就返回 -1。

我是怎么想到 BFS 的

其实这种 "一圈一圈往外扩散、每一步耗时相同、求最短时间" 的题,本能就该想到 BFS。DFS 是一条路走到黑,算不了这种 "同步推进" 的层序时间。

但这题有个小陷阱 ------ 它不是单一起点。一开始网格里可能有好多个烂橘子,它们是同时开始传染的。

我第一次写错,就是傻兮兮只拿第一个遇到的烂橘子当起点跑 BFS。结果算出来的时间直接偏大,完全不对。说白了这是道「多源 BFS」题,初始队列里得把所有一开始就烂掉的橘子都放进去。

真正的坑全在细节里

想通多源这个点之后,整体思路就很顺了:先扫一遍整个网格,数清楚有多少个新鲜橘子,同时把所有烂橘子的坐标都塞进队列。

如果新鲜橘子数一开始就是 0,直接返回 0 就行,不用白跑一趟。别笑,这个边界我第二次提交就忘了,直接喜提 WA。

接着就是层序遍历 BFS:每一轮只处理当前队列里所有的烂橘子 ------ 也就是这一分钟里会去传染别人的这批。挨个往四个方向走,遇到新鲜橘子就把它变烂,新鲜数减一,再放进队列里,下一轮它再去传染别人。每处理完一整层,时间就加 1 分钟。

等 BFS 跑完,看看剩下的新鲜橘子数是不是 0。是 0 就返回总时间,不是就说明有橘子永远感染不到,返回 - 1。

这里特意提一句,每次循环先存当前队列长度这个操作很关键。别问我怎么知道的,我一开始没按层来,每感染一个就给时间 + 1,结果分钟数算得乱七八糟,卡了好半天。

代码实现(JavaScript)

javascript 复制代码
var orangesRotting = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    const queue = [];
    let freshCount = 0;
    let minutes = 0;
    // 上下左右四个方向,写数组遍历就不用写四遍if了
    const dirs = [[-1,0],[1,0],[0,-1],[0,1]];

    // 先扫一遍:统计新鲜橘子数量,所有烂橘子先入队
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(grid[i][j] === 1) {
                freshCount++;
            } else if(grid[i][j] === 2) {
                queue.push([i, j]);
            }
        }
    }

    // 边界情况:一开始就没新鲜橘子,直接返回0
    if(freshCount === 0) return 0;

    while(queue.length) {
        // 记录当前层的橘子数,这步千万别漏!漏了时间就算错了
        const size = queue.length;
        for(let i = 0; i < size; i++) {
            const [x, y] = queue.shift();
            // 四个方向挨个检查
            for(const [dx, dy] of dirs) {
                const nx = x + dx;
                const ny = y + dy;
                // 不越界 且 是新鲜橘子,才会被感染
                if(nx >= 0 && nx < m && ny >=0 && ny < n && grid[nx][ny] === 1) {
                    grid[nx][ny] = 2; // 直接标记为烂橘子,省得重复入队
                    freshCount--;
                    queue.push([nx, ny]);
                }
            }
        }
        // 这一批全感染完了,过了一分钟
        minutes++;
    }

    // 最后还有新鲜橘子就返回-1,否则返回时间
    return freshCount === 0 ? minutes : -1;
};

对了,直接修改原网格当访问标记是很常用的技巧。当然你也可以额外开个 visited 数组,就是多占点空间,也能过。我是觉得反正题目没说不能改原数组,能省则省嘛。

复杂度分析

时间复杂度 O (mn),每个格子最多入队一次,不会重复处理。空间复杂度最坏 O (mn),比如一开局全是烂橘子,队列就装满了。

最后碎碎念

其实这题真的称不上难,就是 BFS 层序遍历的经典变种。但它特别考验你对边界的把控。

我自己踩过的坑给你们列一下,别再踩了:

  1. 只放一个烂橘子进队列,忘了是多源同时出发
  2. 没按层遍历,导致时间计算完全错误
  3. 漏掉 "一开始就没有新鲜橘子" 的边界情况
  4. 最后忘记检查剩余新鲜橘子,直接返回时间

说起来都是小问题,但真写起来很容易忽略。很多中等题都是这样,思路一眼就能看懂,提交就是过不了,全栽在细节里。

你刷这道题的时候是一次就过,还是也踩了坑?或者你有更巧妙的写法?评论区聊聊,我每条都会看。如果觉得这篇题解对你有帮助,点个赞让更多人看到就更好啦。

相关推荐
金銀銅鐵5 小时前
[Python] 扩展欧几里得算法
python·数学·算法
To_OC7 小时前
LC 200 岛屿数量:经典 DFS 入门题,我第一次写居然连方向都搞错了
javascript·算法·leetcode
labixiong9 小时前
实现一个能跑的迷你版Promise(一)
前端·javascript·面试
weedsfly14 小时前
还在用 Axios?你可能需要重新理解 XHR 与 Fetch
前端·javascript·面试
CoderWeen14 小时前
从零实现一个 Vue3 流程图编辑器:节点拖拽、贝塞尔连线与框选
前端·javascript
To_OC1 天前
LC 128 最长连续序列:别上来就排序,O (n) 解法才是这题的灵魂
javascript·算法·leetcode
kyriewen1 天前
我用 50 行代码重写了 React Router 核心,终于搞懂了前端路由原理
前端·javascript·react.js
Asize1 天前
HTML5 Canvas 基础:从按帧动画到 ECharts 数据可视化
前端·javascript·canvas
默_笙1 天前
🎄 后端给我一堆扁平数据,我 10 行代码把它变成了树
前端·javascript