BFS_FloodFill_46 . 腐烂的橘子问题

本节目标:

1 . 了解BFS 、Floodfill概念

2 . 通过这个简易例子,贯通概念并小练一下代码能力


题目介绍

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

  • 0 代表空单元格;
  • 1 代表新鲜橘子;
  • 2 代表腐烂的橘子。

每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。

返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1

示例 2:

cpp 复制代码
输入:grid = [[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个方向上。

示例 3:

cpp 复制代码
输入:grid = [[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10
  • grid[i][j] 仅为 012
cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        
    }
};

本文约 4500字,阅读+思考约16 min------FloodFill初篇

检验答案网址:994. 腐烂的橘子 - 力扣(LeetCode)


解析

1 . 本题需求很明确:给你一个二维数组,里面的每个格子有三种可能:

a . 为1 ,新鲜的橘子。可能被感染

b . 为2 ,腐烂的橘子。作为感染的起点(感染新鲜橘子的起点)

c . 为0 ,无橘子。

2 . 问题从:计算出整箱橘子是否会腐烂完全进行判断------如果腐烂完全,计算出腐烂的时间(min) ;反之,则返回-1

3 . 转化为:FloodFill问题

概念补充

1 . 什么是BFS?什么又是FloodFill?

a . Breadth-First Search(广度优先搜索) ,是一种遍历 / 搜索算法 ,核心规则是:先访问离起点最近的节点,再访问次近的,层层向外扩散

b . BFS------常称为层序遍历。"依次访问最近的结点",什么意思。不妨举个二叉树例子:

i ) "最近的结点" = 和根节点距离(层数)相同的结点,比如根节点是第 0 层,它的左右孩子是第 1 层,这些孩子就是 "最近的结点";

ii ) "依次访问" = 先访问完当前层的所有结点,再访问下一层的结点,绝不跳过当前层去访问更远的结点。

c . BFS遍历的结果:第 0 层(1)→ 第 1 层(2、3)→ 第 2 层(4、5、6)
2 . FloodFill(泛洪填充)是一种应用场景 / 问题类型,不是算法本身

3 . 它描述的是:从一个 / 多个起点出发,按照特定规则(如颜色相同、值相等),向四周扩散,将符合条件的区域 "填充" 为目标状态

4 . 理解看来:BFS是一种算法,而FloodFill是一种可以基于BFS这种算法的问题实例

BFS_FloodFill

1 . 感染源:是刚开始数组里所有的烂橘子(grid[ i ][ j ] == 2)

2 . 在每1分钟内,感染源的四周(左 、右 、上 、下)如果:

有新鲜橘子就会被感染为烂橘子;

无新鲜橘子,或无橘子,则无事

3 . 接着,烂橘子继续扩散污染

4 . 这已经接近FloodFill底层的逻辑------本次是从多个污染源出发,如果四周存在值为1的格子,那么对该区域修改为'2'
5 . 聊聊代码实现。

a . BFS:层序遍历 ------ 自然想到得天独厚的FIFO(先进先出)结构:队列

b . 离得近先出来,不就是FIFO?

c . 根据现在的代码逻辑:先把箱子里所有的2统计出来,作为污染源

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();// 1 <= m , n <= 10;
        std::queue<pair<int,int>> q;//存储污染源
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] == 2)
                {
                    grid[i][j] = -1;//污染源已经存进去,打个标记
                    q.push({i,j});//把值为2的下标push进队列
                }
            }
        }

    }
};

d . 然后开始提取出第0分钟的所有污染源,让其扩散自己的四周

i ) 扩散四周------还得制定一个方向数组

ii ) 第0分钟的所有污染源数量,即q的现有大小

cpp 复制代码
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(true)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                auto [x,y] = q.front();q.pop();
                //x,y为当前bad_source之一,执行污染其他新鲜橘子的逻辑
                // ......直到所有污染源污染一次后,才是第0分钟后污染的结果
            }
        }

6 . 我们相信,在每一次bad_cource之一污染四周时,会导入新的污染源。旧污染源因为污染范围有限------污染一次后就自然pop()掉

7 . 当退出while循环,我们需要得知当前grid(箱子)里,是否存活新鲜橘子------很明显,我们还需要记录新鲜橘子的个数:

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();// 1 <= m , n <= 10;
        std::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)
                {
                    grid[i][j] = -1;
                    q.push({i,j});
                }
                else if(grid[i][j] == 1)
                    ++fresh;// ++
            }
        }
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(true)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                //
            }
        }
        if(fresh)//当退出所有污染源进行污染后,检查箱子里剩余的fresh个数
            return -1;
        return //显然,还需要记录污染用时
    }
};

8 . 添加mininue作为总的污染用时:

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();// 1 <= m , n <= 10;
        std::queue<pair<int,int>> q;
        int fresh = 0;
        int minute = 0;// 记录总的污染用时
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] == 2)
                {
                    grid[i][j] = -1;
                    q.push({i,j});
                }
                else if(grid[i][j] == 1)
                    ++fresh;
            }
        }
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(true)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                //TO DO
            }
            // for循环结束,表明本层的所有污染源污染动作结束------正是1min内污染达到的效果
            ++minute;
        }
        if(fresh)
            return -1;
        return minute;
    }
};

9 . 已知:本层所有的污染源个数,以及每个污染源的坐标------那,只要明白一个污染源的污染逻辑,就可以循环所有污染源执行相同操作

cpp 复制代码
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(true) // ?
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                auto [x,y] = q.front();//取出当前污染源,开始四个方向污染
                for(int d = 0;d < 4;d++)
                {
                    int curr_x = x+direction[d].first;
                    int curr_y = y+direction[d].second;
                    if(curr_x < 0 || curr_x >= m || curr_y < 0 || curr_y >= n)
                        continue;//如果当前没有上格子,或者某个方向格子。跳过该位置
                    else if(grid[curr_x][curr_y] == 1)
                    {
                        --fresh;
                        grid[curr_x][curr_y] = 2;//它现在坏了
                        q.push({curr_x,curr_y});//成为新的污染源
                    }
                    // else if grid[curr_x][curr_y] == 2或者==-1?不用管,只污染新鲜橘子
                }
            }
        }

10 . 所以while的循环条件是什么呢?当队列里的污染源全都进行了污染动作,那么bad_source不再需要执行污染逻辑

还有,当新鲜果子==0也没有执行污染逻辑的必要

cpp 复制代码
        while(!q.empty() && fresh > 0)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                auto [x,y] = q.front();//取出当前污染源,开始四个方向污染
                for(int d = 0;d < 4;d++)
                {
                    int curr_x = x+direction[d].first;
                    int curr_y = y+direction[d].second;
                    if(curr_x < 0 || curr_x >= m || curr_y < 0 || curr_y >= n)
                        continue;//如果当前没有上格子,或者某个方向格子。跳过该位置
                    else if(grid[curr_x][curr_y] == 1)
                    {
                        --fresh;
                        grid[curr_x][curr_y] = 2;//它现在坏了
                        q.push({curr_x,curr_y});//新的污染源
                    }
                    // else if grid[curr_x][curr_y] == 2或者==-1?不用管,只污染新鲜橘子
                }
            }
            // for循环结束,表明本层的所有污染源污染动作结束------正是1min内污染达到的效果
            ++minute;
        }

那么完整的代码:

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();// 1 <= m , n <= 10;
        std::queue<pair<int,int>> q;
        int fresh = 0;
        int minute = 0;
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] == 2)
                {
                    grid[i][j] = -1;
                    q.push({i,j});
                }
                else if(grid[i][j] == 1)
                    ++fresh;
            }
        }
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(q.size() && fresh > 0)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                auto [x,y] = q.front();q.pop();
                for(int d = 0;d < 4;d++)
                {
                    int curr_x = x+direction[d].first;
                    int curr_y = y+direction[d].second;
                    if(curr_x < 0 || curr_x >= m || curr_y < 0 || curr_y >= n)
                        continue;
                    else if(grid[curr_x][curr_y] == 1)
                    {
                        --fresh;
                        grid[curr_x][curr_y] = -1;
                        q.push({curr_x,curr_y});
                    }
                }
            }
            ++minute;
        }
        if(fresh)
            return -1;
        return minute;
    }
};

总结以及完整参考代码

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();// 1 <= m , n <= 10;
        std::queue<pair<int,int>> q;
        int fresh = 0;
        int minute = 0;
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] == 2)
                {
                    grid[i][j] = -1;
                    q.push({i,j});
                }
                else if(grid[i][j] == 1)
                    ++fresh;
            }
        }
        std::vector<pair<int,int>> direction = {{0,-1},{0,1},{1,0},{-1,0}};
        while(q.size() && fresh > 0)
        {
            int bad_source = q.size();
            for(int i = 0;i < bad_source;i++)
            {
                auto [x,y] = q.front();q.pop();
                for(int d = 0;d < 4;d++)
                {
                    int curr_x = x+direction[d].first;
                    int curr_y = y+direction[d].second;
                    if(curr_x < 0 || curr_x >= m || curr_y < 0 || curr_y >= n)
                        continue;
                    else if(grid[curr_x][curr_y] == 1)
                    {
                        --fresh;
                        grid[curr_x][curr_y] = -1;
                        q.push({curr_x,curr_y});
                    }
                }
            }
            ++minute;
        }
        if(fresh)
            return -1;
        return minute;
    }
};

Happy Valentine's Day

祝大家情人节也学业进步、工作顺利

相关推荐
大模型玩家七七1 小时前
关系记忆不是越完整越好:chunk size 的隐性代价
java·前端·数据库·人工智能·深度学习·算法·oracle
样例过了就是过了1 小时前
LeetCode热题100 找到字符串中所有字母异位词
算法·leetcode
DevilSeagull1 小时前
C语言: C语言内存函数详解
c语言·开发语言·算法
搞科研的小刘选手2 小时前
【人工智能专题】2026年人工智能与生成式设计国际学术会议(ICAIGD 2026)
人工智能·算法·aigc·生成式ai·学术会议·计算机工程·生成式设计
橘色的喵2 小时前
一个面向工业嵌入式的 C++17 Header-Only 基础设施库
c++·嵌入式·工业·基础库·head-only
stripe-python2 小时前
十二重铲雪法(上)
c++·算法
愚者游世2 小时前
long long各版本异同
开发语言·c++·程序人生·职场和发展
ccLianLian3 小时前
计算机基础·cs336·RLHF
深度学习·算法
上海合宙LuatOS3 小时前
LuatOS核心库API——【hmeta 】硬件元数据
单片机·嵌入式硬件·物联网·算法·音视频·硬件工程·哈希算法