⭐算法入门⭐《广度优先搜索》中等01 —— LeetCode 994. 腐烂的橘子

饭不食,水不饮,题必须刷

C语言免费动漫教程,和我一起打卡!
光天化日学C语言

LeetCode 太难?先看简单题!
C语言入门100例

数据结构难?不存在的!
数据结构入门

LeetCode 太简单?算法学起来!
夜深人静写算法

文章目录

  • 一、题目
    • 1、题目描述
    • 2、基础框架
    • 3、原题链接
  • 二、解题报告
    • 1、思路分析
    • 2、时间复杂度
    • 3、代码详解
  • 三、本题小知识
  • 四、加群须知

一、题目

1、题目描述

在给定的网格中,每个单元格可以有以下三个值之一:
  1)值 0 代表空单元格;
  2)值 1 代表新鲜橘子;
  3)值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

  样例输入: [ 2 1 1 1 1 0 0 1 1 ] \left[ \begin{matrix} 2 & 1 & 1 \\ 1 & 1 & 0 \\ 0 & 1 & 1\end{matrix} \right] 210111101

  样例输出: 4

2、基础框架

  • c++ 版本给出的基础框架代码如下:
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {

    }
};
  • vector>& grid代表的是一个二维数组,用来代表二维矩阵的橘子位置,&作为引用,用来加速参数传递。
  • 返回值是一个int,代表哪个时刻下所有橘子都腐烂了。

3、原题链接

LeetCode 994. 腐烂的橘子

二、解题报告

1、思路分析

  首先,将所有腐烂的橘子(值为 2 的格子)的位置进行哈希(标记访问时间为 0),然后压入队列。利用广搜,扩散范围。对于相邻的格子,如下几种情况分别处理:
  1)格子没有被访问过,且遇到一个新鲜橘子(值为 1 的格子),哈希后压入队列,并且标记格子的访问时间为相邻那个扩散到它的格子的访问时间 + 1。
  2)格子没有被访问过,且遇到空位置,不做任何处理;
  3)格子被访问过,不做任何处理;
搜索完毕,遍历所有格子,如果存在新鲜橘子,返回 -1;否则,取最访问时间最大的进行返回。

  • 广搜的更多内容,可以参考这篇文章:夜深人静写算法(十)- 单向广搜。

2、时间复杂度

  • 对于一个 n × m n \times m n×m 的矩阵,每个元素只会访问一次,时间复杂度为 O ( n m ) O(nm) O(nm)

3、代码详解

int dir[4][2] = {
    {0, 1},   // right
    {1, 0},   // down
    {0, -1},  // left
    {-1, 0},  // up
};

const int maxn = 120;

class Solution {
    int n, m;
    int visited[maxn];
    queue <int> que;

    int getVisitedId(int x, int y) {                         // (1)
        return x * m + y;
    }
    void getPosByVisitedId(int visitedId, int &x, int &y) {  // (2)
        x = visitedId / m;
        y = visitedId % m;
    }

    void init(vector<vector<int>>& mat) {
        while(!que.empty()) {                                // (3)
            que.pop();
        }
        memset(visited, -1, sizeof(visited));                // (4)
        n = mat.size();                                      // (5)
        m = mat[0].size();                                   // (6)
        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < m; ++j) {
                if(2 == mat[i][j]) {                         // (7)
                    visited[ getVisitedId(i, j) ] = 0;       
                    que.push( getVisitedId(i, j) );
                }
            }
        }
    }
    bool outOfBound(int x, int y) {
        return x < 0 || x >= n || y < 0 || y >= m;           
    }

    void bfs(vector<vector<int>>& mat) {
        while(!que.empty()) {
            int vid = que.front();                           // (8)
            int x, y;
            getPosByVisitedId(vid, x, y);                    // (9)
            que.pop();
            for(int i = 0; i < 4; ++i) {                     // (10)
                int tx = x + dir[i][0];
                int ty = y + dir[i][1];
                if(outOfBound(tx, ty)) {
                    continue;
                }
                int nextvid = getVisitedId(tx, ty);          // (11)
                if(mat[tx][ty] == 1 && visited[nextvid] == -1) {              
                    visited[nextvid] = visited[vid] + 1;     
                    que.push(nextvid);
                }
            }
        }
    }
    int output(int *visited, vector<vector<int>>& mat) {   
        int maxv = 0;
        for(int i = 0; i < n; ++i) {
            vector <int> ans;
            for(int j = 0; j < m; ++j) {
                int vid = getVisitedId(i, j);
                if(mat[i][j] == 1 && visited[ vid ] == -1) { 
                    return -1;                               // (12)
                }
                if( visited[ vid ] > maxv ) {
                    maxv = visited[ vid ];                   // (13)
                }
            }
        }
        return maxv;
    } 

public:
    int orangesRotting(vector<vector<int>>& grid) {
        init(grid);      
        bfs(grid);
        return output(visited, grid);
    }
};
  • ( 1 ) (1) (1) 将二维向量映射到一维,方便索引;
  • ( 2 ) (2) (2) 将一维向量拆解成二维,方便计算;
  • ( 3 ) (3) (3) 定义的队列为类私有成员,所以每次计算,初始化的时候需要首先进行清空;
  • ( 4 ) (4) (4) 利用memset初始化将所有矩阵元素的标记位全部置为-1,关于memset更多用法,可以参考:《C/C++ 面试 100 例》(六)memset 全网最全总结;
  • ( 5 ) (5) (5) 矩阵的行,保存成类的成员变量,方便成员函数使用;
  • ( 6 ) (6) (6) 矩阵的列,保存成类的成员变量,方便成员函数使用;
  • ( 7 ) (7) (7) 找到矩阵中所有的 2(腐烂的橘子),将标记为置为 0,代表腐烂的时刻为 0,然后塞入队列;
  • ( 8 ) (8) (8) 每次从队列头部弹出一个元素;
  • ( 9 ) (9) (9) 将它转换成坐标的形式,方便进行上下左右的运算;
  • ( 10 ) (10) (10) 枚举四个方向扩散;
  • ( 11 ) (11) (11) 得到一个相邻位置,如果这个位置没有被访问过,且值为 1 (代表新鲜橘子),则将步数置为 当前位置步数 + 1;
  • ( 12 ) (12) (12) 搜索完毕,遍历所有格子,如果存在新鲜橘子,返回 -1;
  • ( 13 ) (13) (13) 否则,取最访问时间最大的进行返回。

三、本题小知识

1)一维的vector可以当数组用,二维的vector可以当矩阵用。
2)利用memset可以将标记置为-1代表尚未访问。
3)广搜的过程,就是不断把状态压入队列尾,不断取出队列头部状态的过程,由于状态可能是多维的,所以我们一般考虑将多维状态变成一维,这个称为序列化;再在弹出队列的时候,将一维状态还原成多维,这个称为反序列化。一般就是通过进制转换来完成。


四、加群须知

  相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」
  那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:

算法入门指引

  如果链接被屏蔽,或者有权限问题,可以私聊作者解决。

  大致题集一览:


在这里插入图片描述


  为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
  不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」
  联系作者,或者扫作者主页二维码加群,加入刷题行列吧


让天下没有难学的算法

C语言免费动漫教程,和我一起打卡!
光天化日学C语言

入门级C语言真题汇总
C语言入门100例

几张动图学会一种数据结构
画解数据结构

组团学习,抱团生长
算法入门指引

竞赛选手金典图文教程
夜深人静写算法

你可能感兴趣的:(《LeetCode算法全集》,算法,数据结构,队列,leetcode,广度优先搜索)