leetcode刷题1——994.腐烂的橘子(广度优先搜索)

1.前言

从该题学到了多源广度优先搜索BFS,stl中queue,以及pair的用法,语言为C++

2.题目描述

难度:简单

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

值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。

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

示例 1:

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

输入:[[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。
示例 3:

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

提示:

1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 仅为 0、1 或 2
通过次数20,119提交次数39,759

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotting-oranges
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

3.自己解

没有想到用BFS,找到所有新鲜橘子,寻找周围是否有怀橘子,有——变坏;没有——保持;

class Solution {
public:
    int find1(vector<vector<int>> &grid,int x,int y,int xn,int yn)
    {//检测所有的1周围是否有2(返回2)、或10(返回1)、或只有0(返回0)
        vector<int> num(4,-1);//遇到墙壁不赋值,为-1
        if(x-1>=0)//上
            num[0]=grid[x-1][y];
        if(x+1<xn)//下
            num[1]=grid[x+1][y];
        if(y-1>=0)//左
            num[2]=grid[x][y-1];
        if(y+1<yn)//右
            num[3]=grid[x][y+1];
        int flag=-1;//通过flag记录1周围点的最大值,就可以判断时下列三种情况的哪一种
        for(int i=0;i<4;i++)//检测所有的1周围是否有2(返回2)、或10(返回1)、或只有0(返回0)
        {
            if(num[i]>flag)
                flag=num[i];
        }
        return flag;
    }
    int orangesRotting(vector<vector<int>>& grid) {
        int num1 = 0;
        int num2 = 0;//记录grid中有多少1和2
        int xn=grid.size();
        int yn=grid[0].size();
        int i,j;
        //存在1,周围全是0,返回-1,没有2,也不会发生腐烂,返回-1;grid里面一个1也没有,返回0;
        for(i = 0;i<xn;i++)
        {
            for(j=0;j<yn;j++)
            {
                if(grid[i][j]==1)
                {
                    if(find1(grid,i,j,xn,yn)==0)
                        return -1;//存在1,周围全是0,返回-1
                    else
                        num1++;
                }
                else if(grid[i][j]==2)
                    num2++;
            }
        }
        if(num1==0) return 0;//grid里面一个1也没有,返回0;
        if(num2==0) return -1;//没有2,也不会发生腐烂,返回-1

        int count=0;//end=1结束的标志是grid中没有1了,即num1==0
        vector<vector<int>> temp(grid.begin(),grid.end());//辅助空间,每次传播一次更新一次
        while(num1>0)//此时每个1周围要么有2,要么没有2,附近只有0,1;
        {
            int change=0;//每次循环都有橘子坏掉,若没有坏掉且有新鲜橘子num1>0,说明存在不连通的子图
            for(i = 0;i<xn;i++)
            {
                for(j=0;j<yn;j++)
                {
                    if(grid[i][j]==1)
                    {
                        if(find1(grid,i,j,xn,yn)==2)
                        {
                            temp[i][j]=2;
                            num1--;
                            change=1;
                        }
                    }
                }
            }
            if(change == 0)//没有橘子坏掉,存在不连通的子图
                return -1;
            count++;
            grid.clear();
            for(i=0;i<xn;i++)
            {
                grid.push_back(temp[i]);
            }//grid一轮传播后更新

        }
        return count;

    }
};

执行结果

自己写的方法比较笨

4.官方解

方法一:枚举 + 广度优先搜索

思路

由题目我们可以知道每分钟每个腐烂的橘子都会使上下左右相邻的新鲜橘子腐烂,这其实是一个模拟广度优先搜索的过程。所谓广度优先搜索,就是从起点出发,每次都尝试访问同一层的节点,如果同一层都访问完了,再访问下一层,最后广度优先搜索找到的路径就是从起点开始的最短合法路径。

回到题目中,假设图中只有一个腐烂的橘子,它每分钟向外拓展,腐烂上下左右相邻的新鲜橘子,那么下一分钟,就是这些被腐烂的橘子再向外拓展腐烂相邻的新鲜橘子,这与广度优先搜索的过程均一一对应,上下左右相邻的新鲜橘子就是该腐烂橘子尝试访问的同一层的节点,路径长度就是新鲜橘子被腐烂的时间。我们记录下每个新鲜橘子被腐烂的时间,最后如果单元格中没有新鲜橘子,腐烂所有新鲜橘子所必须经过的最小分钟数就是新鲜橘子被腐烂的时间的最大值。

以上是基于图中只有一个腐烂的橘子的情况,可实际题目中腐烂的橘子数不止一个,看似与广度优先搜索有所区别,不能直接套用,但其实有两个方向的思路。

一个是耗时比较大且不推荐的做法:我们对每个腐烂橘子为起点都进行一次广度优先搜索,用 dis[x][y][i]dis[x][y][i] 表示只考虑第 ii 个腐烂橘子为起点的广度优先搜索,坐标位于 (x, y)(x,y) 的新鲜橘子被腐烂的时间,设没有被腐烂的新鲜橘子的 dis[x][y][i]=infdis[x][y][i]=inf ,即无限大,表示没有被腐烂,那么每个新鲜橘子被腐烂的最短时间即为

min_{i}\ dis[x][y][i]
min 
i
​    
  dis[x][y][i]

最后的答案就是所有新鲜橘子被腐烂的最短时间的最大值,如果是无限大,说明有新鲜橘子没有被腐烂,输出 -1−1 即可。

无疑上面的方法需要枚举每个腐烂橘子,所以时间复杂度需要在原先广度优先搜索遍历的时间复杂度上再乘以腐烂橘子数,这在整个网格范围变大的时候十分耗时,所以需要另寻他路。

方法二:多源广度优先搜索

思路

观察到对于所有的腐烂橘子,其实它们在广度优先搜索上是等价于同一层的节点的。

假设这些腐烂橘子刚开始是新鲜的,而有一个腐烂橘子(我们令其为超级源点)会在下一秒把这些橘子都变腐烂,而这个腐烂橘子刚开始在的时间是 -1−1 ,那么按照广度优先搜索的算法,下一分钟也就是第 00 分钟的时候,这个腐烂橘子会把它们都变成腐烂橘子,然后继续向外拓展,所以其实这些腐烂橘子是同一层的节点。那么在广度优先搜索的时候,我们将这些腐烂橘子都放进队列里进行广度优先搜索即可,最后每个新鲜橘子被腐烂的最短时间 dis[x][y]dis[x][y] 其实是以这个超级源点的腐烂橘子为起点的广度优先搜索得到的结果。

为了确认是否所有新鲜橘子都被腐烂,可以记录一个变量 cntcnt 表示当前网格中的新鲜橘子数,广度优先搜索的时候如果有新鲜橘子被腐烂,则 cnt-=1 ,最后搜索结束时如果 cntcnt 大于 00 ,说明有新鲜橘子没被腐烂,返回 -1−1 ,否则返回所有新鲜橘子被腐烂的时间的最大值即可,也可以在广度优先搜索的过程中把已腐烂的新鲜橘子的值由 1 改为 2,最后看网格中是否由值为 1 即新鲜的橘子即可。

采用方法2,源代码为


class Solution {//多源广度优先搜索
    int cnt;//新鲜橘子数
    int dis[10][10];//坐标位于x,y的橘子的腐烂时间<=>距离
    int dir_x[4]={0, 1, 0, -1};
    int dir_y[4]={1, 0, -1, 0};//方向向量,右,下,左,上
public:
    int orangesRotting(vector<vector<int>>& grid) {
        queue<pair<int,int> >Q;//腐烂的橘子进入队列进行广度优先搜索,这里存的是腐烂橘子的坐标
        memset(dis, -1, sizeof(dis));//假设所有橘子都是新鲜的,-1,即离腐烂橘子无穷远
        cnt = 0;
        int n=(int)grid.size(), m=(int)grid[0].size(), ans = 0;//n行m列,ans是传播完所有橘子的时间

        for (int i = 0; i < n; ++i){
            for (int j = 0; j < m; ++j){
                if (grid[i][j] == 2){
                    Q.push(make_pair(i, j));
                    dis[i][j] = 0;//0时刻,污染的橘子进队列,初始化
                }
                else if (grid[i][j] == 1) cnt += 1;//统计新鲜橘子
            }
        }
        while (!Q.empty()){
            pair<int,int> x = Q.front();Q.pop();//取队首并弹出
            //从污染的橘子开始进行一轮广度优先搜索
            for (int i = 0; i < 4; ++i){//方向向量,右,下,左,上
                int tx = x.first + dir_x[i];
                int ty = x.second + dir_y[i];
                if (tx < 0|| tx >= n || ty < 0|| ty >= m|| ~dis[tx][ty] || !grid[tx][ty]) continue;
                //~(-1)=0,其余数字取反不等于0,即~dis[tx][ty]等价于dis[tx][ty]!=-1
                //不进行下列代码的条件,越界、撞墙即grid=0、探索过的橘子dis[tx][ty]!=-1
                dis[tx][ty] = dis[x.first][x.second] + 1;//这一轮被搜索到的橘子是上一轮橘子距离加一
                Q.push(make_pair(tx, ty));//被污染的橘子进队列
                if (grid[tx][ty] == 1){//如果是新鲜橘子,则数量-1,答案更新
                    cnt -= 1;
                    ans = dis[tx][ty];
                    if (!cnt) break;//如果新鲜橘子数量=0,结束搜索
                }
            }
        }
        //cnt>1,则说明有新鲜橘子,返回-1;cnt==0,说明没有新鲜橘子,返回ans
        return cnt ? -1 : ans;
    }
};

运行结果

还行吧

思路画成流程图为

发布了47 篇原创文章 · 获赞 28 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41122796/article/details/104673673