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. 最后忘记检查剩余新鲜橘子,直接返回时间

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

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

相关推荐
大模型码小白3 分钟前
AI 对话流性能调优:万级消息的虚拟滚动落地
java·大数据·前端·javascript·人工智能·算法·机器学习
m0_5474866619 分钟前
《JavaScript核心原理 》全套PPT课件2026
开发语言·javascript·ecmascript
sel_91 小时前
【多轮对话论文导读(三)】多轮对话与Agent论文阅读笔记:用户模拟、轨迹生成与长期记忆
论文阅读·人工智能·笔记·深度学习·算法·机器学习
xm_xm_xm_11 小时前
react17版本以前类组件常用操作
前端·javascript·react.js
Asize1 小时前
438. 找到字符串中所有字母异位词
算法
Asize1 小时前
283. 移动零
算法
lzhdim2 小时前
13、JavaScript事件循环机制 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
liangshanbo12153 小时前
Vue 3 <script setup> 的本质原理是什么?它与普通 setup()有何区别?
前端·javascript·vue.js
船厂电气自动化ai大模型3 小时前
AI大模型与数学·第57课 傅里叶全套工具链综合实战:串联级数/连续变换/DFT/FFT,图像、音频、扩散模型完整例题
数据结构·人工智能·深度学习·算法·机器学习
人工智能培训4 小时前
人工智能数据安全下的个人信息保护实践方案
大数据·人工智能·算法·生活