算法入门广度优先搜索(简单 - 第一题)LeetCode 542

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法入门广度优先搜索(简单 - 第一题)LeetCode 542相关的知识,希望对你有一定的参考价值。

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

还不会C语言,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?上简单题!
🧡《C语言入门100例》🧡

LeetCode 太简单?大神盘他!
🌌《夜深人静写算法》🌌

一、题目

1、题目描述

  给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。两个相邻元素间的距离为 1 。
  样例: [ 0 0 0 0 1 0 1 1 1 ] \\left[ \\begin{matrix} 0 & 0 & 0 \\\\ 0 & 1 & 0 \\\\ 1 & 1 & 1\\end{matrix} \\right] 001011001

  输出: [ 0 0 0 0 1 0 1 2 1 ] \\left[ \\begin{matrix} 0 & 0 & 0 \\\\ 0 & 1 & 0 \\\\ 1 & 2 & 1\\end{matrix} \\right] 001012001

2、基础框架

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

    }
};
  • vector<vector<int>>& mat代表的是一个二维数组,用来代表题意所要求的矩阵,&作为引用,用来加速参数传递。
  • 返回值是一个vector<vector<int>>,代表返回也是一个二维矩阵。可以在函数内部动议一个返回值vector<vector<int>> ret,然后再去填充这个ret。因为这块内存是在堆上申请的,返回时不会被销毁。

3、原题链接

LeetCode 542. 01 矩阵

二、解题报告

1、思路分析

  寻找每个非 0 元素最近的 0 的过程,可以采用广度优先搜索,因为广搜找的是最短路,找到的第一个 0 一定是最近的。
  如果一个矩阵右下角是 0,其余全是 1,那么左上角的 1 寻找 0 的时间复杂度就是 O ( n m ) O(nm) O(nm),最坏情况下 n × m n \\times m n×m 个元素,所以最坏时间复杂度为 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)。一共最多有 100000 个元素,所以肯定是无法接受的。
  例如,图中黄色块代表非0,红色块代表 0,想要枚举每个点进行广搜,枚举过程 O ( n m ) O(nm) O(nm),广搜过程 O ( n m ) O(nm) O(nm),所以总的时间复杂度为 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)

  所以我们可以考虑反着来,将所有的 0 哈希后压入队列,然后从 0 开始搜,遇到哈希过的就不重复搜索了,没有哈希过的,哈希完继续压入队列,同时记录步数,这样,当队列为空的时候,每个矩阵元素都被访问完毕了,而且都只会被访问一次。

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 = 10010;

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(!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(visited[nextvid] == -1) {              
                    visited[nextvid] = visited[vid] + 1;     
                    que.push(nextvid);
                }
            }
        }
    }
    void output(int *visited, vector<vector<int>>& ret) {   // (12)
        for(int i = 0; i < n; ++i) {
            vector <int> ans;
            for(int j = 0; j < m; ++j) {
                ans.push_back( visited[ getVisitedId(i, j) ]);
            }
            ret.push_back(ans);
        }
    } 

public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        vector<vector<int>> ret;
        init(mat);      
        bfs(mat);
        output(visited, ret);
        return ret;
    }
};
  • ( 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) 找到矩阵中所有的 0,将标记为置为 0,代表最近的零的距离为 0,然后塞入队列;
  • ( 8 ) (8) (8) 每次从队列头部弹出一个元素;
  • ( 9 ) (9) (9) 将它转换成坐标的形式,方便进行上下左右的运算;
  • ( 10 ) (10) (10) 枚举四个方向扩散;
  • ( 11 ) (11) (11) 得到一个相邻位置,如果这个位置没有被访问过,则将步数置为 当前位置步数 + 1;
  • ( 12 ) (12) (12) output函数用于将搜索结果存储到ret中用于返回用;

三、本题小知识

1)一维的vector可以当数组用,二维的vector可以当矩阵用。
2)最短路的逆向思维,正着走费劲的时候我们就反着走。
3)利用memset可以将标记置为-1代表尚未访问。


以上是关于算法入门广度优先搜索(简单 - 第一题)LeetCode 542的主要内容,如果未能解决你的问题,请参考以下文章

算法入门 05深度优先搜索(简单 - 第一题)LeetCode 733

算法入门 05深度优先搜索(中等 - 第一题)LeetCode 695

算法入门广度优先搜索(中等 - 第二题)LeetCode 116

LeetCode每日一题——851. 喧闹和富有

力扣每日一题:993. 二叉树的堂兄弟节点(简单)

广度优先搜索的实际应用