专题十七_BFS_BFS解决 FloodFill 算法_BFS 解决最短路问题_多源 BFS_BFS 解决拓扑排序

目录

BFS

一、BFS解决 FloodFill 算法

1. 图像渲染(medium)

解析:

细节问题:

总结:

2. 岛屿数量(medium)

解析:

DFS:

BFS:

总结:

3. 岛屿的最⼤⾯积(medium)

解析:

总结:

4. 被围绕的区域(medium)

​编辑解析:

总结:

二、BFS解决最短路径问题:​编辑

5. 迷宫中离⼊⼝最近的出⼝(medium)

解析:

总结:

6. 最⼩基因变化(medium)

解析:

总结:

7. 单词接⻰(hard)

解析:

大优化:

总结:

8. 为⾼尔夫⽐赛砍树(hard)

解析:

总结:

三、多源 BFS

9. 01 矩阵(medium)

解析:

解法一:

解法二:正难则反;

总结:

10. ⻜地的数量(medium)

解析:

解法一:暴力搜索

解法二:正难则反

最终代码:

总结:

11. 地图中的最⾼点(medium)

解析:

总结:

12. 地图分析(medium)

解析:

总结:

四、BFS 解决拓扑排序:

13. 课程表(medium)

解析: 

2.建图:

3.开始拓扑排序,如果有环,就返回-1,说明失败了

4.最后就是检查是否存在环

总结:

14. 课程表II(medium)

解析: 

总结:

15. ⽕星词典(hard)

解析:

算法原理介绍:

总结:

总结一下吧~总结不易,本章对我的学习进步收获很大,希望对你也是~


BFS

这里我们来进入BFS宽度优先遍历专题,前面我们学习了深度优先遍历,这里要进行解决的目标有:

1.BFS解决 FloodFill 算法

2.BFS 解决最短路问题

3.多源 BFS

4.BFS 解决拓扑排序

其中解决FloodFill算法是再DFS算法内完完整整做过的所有题目,我们只是换了一种遍历方式来进行编写代码,但是具体思路都是一模一样的,一定自己先尝试上手书写代码,再来看解析;

那么这一个BFS宽度优先遍历的算法主要就是用来解决 最短路径解决拓扑排序 问题的,现在让我们一起来看看吧;

一、BFS解决 FloodFill 算法

1. 图像渲染(medium)

题目意思很简单就是遍历这个二维矩阵,然后将连通的岛屿的颜色进行修改,然后最后返回这个矩阵

解析:

还是跟DFS大差不差,主要就是换种方式进行遍历这个二维矩阵,我们的目的就都是为了遍历完矩阵后,将所有跟image[sr][sc]相等的值修改成color即可。那么我们就可以对这个二维矩阵进行BFS层序遍历,来遍历当前位置的下一层,就是采用队列,然后入队列,将下一层所有的值加入到队列后,当前值出队列,然后进行修改颜色;

例如:

我每次都只是去寻找当前位置的上下左右四个位置,就是我的下一层,其实这里就可以看到跟DFS算法简直一模一样,只不过DFS是要进行递归式的遍历,而这里BFS就只是借助了一个队列来帮助我们完成了一个仿造递归的模式,完成了遍历二维数组的方式。

那么就要求构造向量数组来进行遍历:

    int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};
细节问题:

再第一个当前的目标位置进入队列的时候,就已经要考虑到当前位置的颜色已经被改变,要记得进行修改;

然后后面就是模板问题:

            auto [a,b] = q.front();q.pop();

永远记住取队头元素和删除队头元素永远都是一对,千万不要分开了,以免自己忘记;

这里的auto [a,b] 语法就是分别取a=q.front.first()  b=q.front.second() 这样用aoto [a,b]=q.front()方便多了。

然后就是对当前位置的前后左右进行遍历,遇到满足条件的值就进行修改:

  

if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==prev)
class Solution {
public:int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {int m=image.size(),n=image[0].size();int prev=image[sr][sc];if(prev==color) return image;queue<pair<int,int>> q;image[sr][sc]=color;q.push({sr,sc});while(q.size()){auto [a,b] = q.front();q.pop();for(int i=0;i<4;i++){int x=dx[i]+a;int y=dy[i]+b;if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==prev){q.push({x,y});image[x][y]=color;}}}return image;}
};

总结:

不论是DFS还是BFS,都是对二维数组进行完整的遍历,遇到满足条件的情况就进行修改,都是大差不差的思路,主要还是要学会解题的思想,这样不论哪种方法都能信手拈来。

2. 岛屿数量(medium)

题目意思很简单,就是遍历整个二维数组,返回有多少个完全被0隔开的岛屿1

解析:

DFS:

一眼就是遍历整个二维数组,然后对整个二维数组进行观察,从当前位置字符是‘1’为入口然后开始进入,如果安装DFS深度优先遍历的话,以一个入口进入后,然后疯狂递归,直到修改完当前岛屿所有位置的‘1’ 为true,表示已经被访问过了;然后在进行回退到最初的入口,再两层for的条件下,寻找新的入口,那么能够找到几个入口,就证明有几个岛屿。

BFS:

层序遍历也依然如此,我们说,层序遍历就是仿造递归来进行的,但是并没有进行递归调用,只是在一个队列里面完成了所有的操作。

依旧是在两层for循环内寻找入口,只要找到一个入口,我们就进行进入,然后层序遍历当前位置的上下左右:

    int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};

从入口进入后,进行添加和修改当前位置的值为true,表示已经被访问过;

 bool index[301][301];

然后就跟上题一样了,访问上下左右四个位置,满足条件就进入,进行修改为true表示已经访问,并且加入到队列,好为了再下一层做准备 

if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]=='1'&&index[x][y]==false){index[x][y]=true;q.push({x,y});}
class Solution {
public:int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};bool index[301][301];int numIslands(vector<vector<char>>& grid) {int m=grid.size(),n=grid[0].size();queue<pair<int,int>> q;int ret=0;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(index[i][j]==false&&grid[i][j]=='1'){ret++;q.push({i,j});index[i][j]=true;while(q.size()){auto [a,b] = q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]=='1'&&index[x][y]==false){index[x][y]=true;q.push({x,y});}}}}}}return ret;}
};

总结:

只要上一个专题DFS能够独立写的出来,这里BFS解决FloodFill算法简直so easy~

3. 岛屿的最⼤⾯积(medium)

标题意思就是题目的意思,很简单,只需要考虑每一个入口,然后进行遍历所有的1的放歌,进行取最大值即可。

解析:

跟上题一模一样,只是再两层for内寻找入口,只要有一个满足:

 if(index[i][j]==false&&grid[i][j]=='1')

从该入口进入后,遍历整个岛屿,按照层序遍历的方式,每次加入一个位置节点,即:

queue<pair<int,int>> q;

按照层序遍历的方式遍历,就访问当前位置的上下左右四个位置的值的时候如果满足:

if(x>=0&&x<m&&y>=0&&y<n&&index[x][y]==false&&grid[x][y]==1)

该位置即没有被访问,也是整数1 那么就是该岛屿的一部分,就可以加入队列q内,并设置该位置是true表示已经被访问,防止重复访问。

index[x][y]=true;
 q.push({x,y});

目的是为了,下次以这个{x,y}位置为起点,能够访问它周围的位置,继续进行层序遍历,虽然没有进行递归但还是有点递归那味道。

class Solution {
public:int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};bool index[51][51];int maxAreaOfIsland(vector<vector<int>>& grid) {int ret=0;int m=grid.size(),n=grid[0].size();queue<pair<int,int>> q;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(index[i][j]==false&&grid[i][j]==1){q.push({i,j});int sum=0;index[i][j]=true;while(q.size()){auto [a,b]=q.front();q.pop();sum++;for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&index[x][y]==false&&grid[x][y]==1){index[x][y]=true;q.push({x,y});}}}ret=max(ret,sum);}}}return ret;}
};

总结:

题目并不难,只要还是思想问题,如果能够一下就想到递归解决暴力来访问解决,计算最大岛屿面积,那么BFS也不再话下。

4. 被围绕的区域(medium)

题目意思很简单,就是如果这个区域被连通到了这个二维矩阵的边界上,那么这个‘O’就不能被修改为‘X’,只能修改全部都被‘X’给包围的所有‘O’

解析:

这题就有点正难则反的那个味道了,如果是按照顺序写过来,前面一个DFS专题里面这题的思路就是如果我是正向遍历,从每个如果进去然后进行修改,在判断当前位置是不是连通到边界,这样会很麻烦,时间复杂度也贼高,还不能保证正确,因为最大的问题就是,我没有办法知道当前位置是不是跟边界相联,也不晓得当前的字符‘O’应不应该进行修改;

所以我们就要从边界出发,我们只用访问边界的位置,判断 所有边界的条件下,连通的所有子块‘O’都把他置为true,表示当前位置的字符‘O’是连向边界的不能进行修改,那么对四个边界的入口进入后,完成层序遍历,其实这里DFS专题里面也一样,从这里进取完成深度遍历,效果都是一样的,只不过实现的方法不同,将他全部置为true;

最后再两层for上进行遍历整个二维矩阵,只要碰到false的‘O’就表示当前位置可以被修改。怎么样是不是很简单。

class Solution {
public:bool index[201][201];int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};int n,m;void solve(vector<vector<char>>& board) {m=board.size(),n=board[0].size();queue<pair<int,int>> q;for(int i=0;i<n;i++){if(board[0][i]=='O'&&index[0][i]==false)bfs(q,board,0,i);if(board[m-1][i]=='O'&&index[m-1][i]==false)bfs(q,board,m-1,i);}for(int i=0;i<m;i++){if(board[i][0]=='O'&&index[i][0]==false)bfs(q,board,i,0);if(board[i][n-1]=='O'&&index[i][n-1]==false)bfs(q,board,i,n-1);}for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(board[i][j]=='O'&&index[i][j]==false) board[i][j]='X';}}}void bfs(queue<pair<int,int>> q,vector<vector<char>>& board,int i,int j){index[i][j]=true;q.push({i,j});while(q.size()){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='O'&&index[x][y]==false){index[x][y]=true;q.push({x,y});}}}}
};

总结:

还是跟DFS的思想一样,只要前面专题稳扎稳打过来,这个专题绝对轻松拿捏~


二、BFS解决最短路径问题:

从起点开始来一次BFS,然后用一个bool 数组hash [] 进行记录,将每一层的节点同时出队列,然后每次将出队列的节点的下一层要进行入队列,带入队列时,要判断,当前入队列的节点是否已经被访问过,如果是,那么就说明当前这一条路要比别的路要慢。直到最先走到终点的路,当前层数就是最短路径距离。

5. 迷宫中离⼊⼝最近的出⼝(medium)

边权为1 的最短路问题:

题目有点长,之间看我的题目翻译:
就是给我们一个位置,然后从这个位置开始出发,直到走到出口,找到最短的路径,只用走到边界位置即可。

解析:

根据上面的 边权为1 的最短路问题,可以知道,只要我们运用一次BFS遍历,然后记录层数,只要最先能到达边界位置的就是最短路径。

那么首先就要考虑bool index[][]数组 和 队列 queue<pair<int,int>> q; 先将最原始的位置进行入队列,然后计算当前层的个数sz=q.size();

然后再for循环内完整遍历当前层,进行出队列和下一层的节点入队列,并且将已经入队列的节点改为true表示已经被访问过,不会再别的路径再次会访问,直到最短路径先遍历到边界下

if(x==0||x==m-1||y==0||y==n-1) return ret;

边界条件进行返回。

class Solution {
public:bool index[101][101];int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {int m=maze.size(),n=maze[0].size();queue<pair<int,int>> q;q.push({entrance[0],entrance[1]});int ret=0;index[entrance[0]][entrance[1]]=true;while(q.size()){int sz=q.size();ret++;for(int i=0;i<sz;i++){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&index[x][y]==false&&maze[x][y]=='.'){if(x==0||x==m-1||y==0||y==n-1) return ret;index[x][y]=true;q.push({x,y});}}}}return -1;}
};

总结:

总的来说,如果floodfill算法学的不错的话,这个也是很简单的,就是多了一个要记录层数的条件。

6. 最⼩基因变化(medium)

题目很长,直接看我的题目解析,就是每次start字符串能够改变一个字符,然后成为基因库里面的某一个字符串,那么最少需要改变几次成为end字符串

解析:

经典的边权为1 的最短路径问题:

我只能说我真的有点牛逼了,一次过;

如果读完题目想不到要用BFS求出最短路径的思路,那就是题目做少了,所以大家还要一起努力,大多提升自己的编码能力和思考思路。

为什么要用BFS呢?

因为他要我们求出最少改变的次数,并且题目内的基因库,每次只能修改一个基因,说明我们的下一层可以考虑的全都是只有一个字符跟我们当前层不一样的字符串

也就是说,我们每次只能进入只有一个字符串不一样的那一层,那么我们就可以通过一个

hash<string,bool>

数组来将基因库里面的所有字符串全部都存入并置为false,那么就进行将所有于当前只差一个字符的字符串全部都入队列,就是证明下一层的所有字符都被入了队列,然后再次进行判断,判断出与这个新的一层只差一个字符的字符串有哪些,就这样循环往复,直到入队列的某一刻,遇到了与end字符串相等的那一个字符串,那么就可以进行返回当前的层数,此时从开始到结束,都是用ret来记录进入队列进入了多少层。

class Solution {
public:int minMutation(string startGene, string endGene, vector<string>& bank) {int n=bank.size();queue<string> q;q.push(startGene);unordered_map<string,bool> hash;for(auto e : bank) hash[e]==false;hash[startGene]=true;int ret=0;while(q.size()){int sz=q.size();ret++;for(int i=0;i<sz;i++){string s=q.front();q.pop();for(auto e : bank){int num=0;if(hash[e]==false){for(int i=0;i<startGene.size();i++){if(s[i]!=e[i]) num++;}if(num==1){q.push(e);hash[e]=true;if(e==endGene) return ret;}}}}}return -1;}
};

总结:

其实只要自己仔细思考,然后画图总结,就可以发现真的就是BFS求出最短路径问题,如果第一次没做出来也没关系,主要是学习这种思想。

7. 单词接⻰(hard)

这一题跟上一题一模一样,感觉用同一套代码都能过。

解析:

这一题只不过数据范围更大了,每一个字符都可以修改为26个字母的任意一个,那么就还是跟上体一模一样,他只是说每次都只能改变一个字符,并且改变后的字符串,要在wordList里面存在,才能完成改变,那么每次就都进行遍历这个wordList的单词库,只要有一个字符不一样的字符串,它就有机会最后变成end字符串,所以就将每一个没有被访问,即false 且跟当前字符串s只相差一个字符的e字符串存入队列内q

并且修改hash<string,bool> 修改当前hash[e]=true

每次存入所有只相差一个字符 的所有字符串是在同一层内,那么就用ret记录层数,每次完成一层,ret就++,最后直到入队列的时候发现了跟end相同的字符串,就返回ret得到了最短的距离 

class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {int n=wordList.size();queue<string> q;q.push(beginWord);unordered_map<string,bool> hash;for(auto e : wordList) hash[e]=false;hash[beginWord]=true;int ret=1;while(q.size()){ret++;int sz=q.size();for(int i=0;i<sz;i++){string s=q.front();q.pop();for(auto e : wordList){int num=0;for(int j=0;j<beginWord.size();j++){if(e[j]!=s[j]) num++;}if(num==1&&hash[e]==false){if(e==endWord) return ret;hash[e]=true;q.push(e);}}}}return 0;}
};

大优化:

上一个方案里面,是每次都要完全访问整个字符串库,然后来进行判断,如果库真的很大很大的时候,时间复杂度贼高,绝对超时,那么这里就只改成每个字符26次修改,是常数级,就会很快,如果存在且为false就进行加入,否则就继续进行修改

class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {int n=wordList.size();queue<string> q;q.push(beginWord);unordered_map<string,bool> hash;for(auto e : wordList) hash[e]=false;hash[beginWord]=true;int ret=1;while(q.size()){ret++;int sz=q.size();for(int i=0;i<sz;i++){string t=q.front();q.pop();for(int j=0;j<t.size();j++){string tmp=t;for(char ch='a';ch<='z';ch++){tmp[j]=ch;if(hash.count(tmp)&&hash[tmp]==false){if(tmp==endWord) return ret;q.push(tmp);hash[tmp]=true;}}}}}return 0;}
};

总结:

这一题跟上一题一模一样,希望大家都能好好吸收,如果有更好的方法,欢迎留言。

8. 为⾼尔夫⽐赛砍树(hard)

题目很长 ,直接看题目解析:

意思就是这个二维矩阵,是m*n,那么0表示障碍物,不能行走,1表示地面可以走,比1大的数字就是树,就要砍掉,那么不能随便砍树,只能按照顺序进行砍树,就是比较树的大小,必须要从最小的树开始砍起,算上这样走的总步数,如果最后没有全部砍完,就说明失败了,返回-1;

解析:

整体来说,这一题就是语法知识比较多,用c++17的语法auto [a,b]=q.front();直接进行返回就不用再写int a=q.front().first,b=q.front().second;

首先需要明白的是从开始位置进行遍历,所需要遍历的值的顺序,那么我们这里就要将每一个遍历的数值按照下标的方式存在结果里,就相当于v[0...n]={i,j};

        vector<pair<int,int>> trees;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(forest[i][j]>1) trees.push_back({i,j});}}    

然后对数组进行排序,但是只能特定的进行排序,按照f树里面的树的大小进行返回,这里用了lambda表达式来进行捕获f数组里面的值,直接进行返回比较的大小是true还是false

        // 按树高度排序,使用 lambda 表达式捕获 fsort(trees.begin(), trees.end(), [&](const pair<int, int>& a, const pair<int, int>& b) {return f[a.first][a.second] < f[b.first][b.second];});

 对这个数组的下标进行排序完成后,接下来就是要从第一个树走到第二颗树,依次类推:
就开始遍历整个数组,从最初的(0,0)位置开始进行寻找下一个目标auto [a,b] : trees

那么[a,b] 里面就是存的下一个要砍的树的坐标,传入bfs,就是变的跟前面几题一模一样,只需要求到目标位置要走的最少步骤。

        //按照顺序砍树int bx=0,by=0;int ret=0;for(auto& [a,b] : trees){int step = bfs(forest,bx,by,a,b);if(step==-1) return -1;ret+=step;bx=a,by=b;}return ret;}

 然后累加上所有经过树的要走的步骤次数,然后进行返回,如果bfs返回-1,就说明走不到该树,返回-1,就失败了。

前面都是核心部分,最后实现bfs就还是那么简单和无脑:

int bfs(vector<vector<int>>& f,int bx,int by,int ex,int ey)

依旧是创建一个 bool数组vis[][]进行判断当下一个位置是否可以经过:

 if(x>=0&&x<m&&y>=0&&y<n&&f[x][y]&&!vis[x][y])

每次进入bfs都是一次新的开始,所以每次都要将vis清空:

 memset(vis,0,sizeof(vis));

后面就是照抄前面几题的代码了,入队列,置为true,然后每一层就step++,然后判断上下左右的四个位置是否满足入队列的条件,如果满足就进队列,并置为true

    bool vis[51][51];int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};int bfs(vector<vector<int>>& f,int bx,int by,int ex,int ey){if(bx==ex&&by==ey) return 0;queue<pair<int,int>> q;memset(vis,0,sizeof(vis));q.push({bx,by});vis[bx][by]=true;int step=0;while(q.size()){step++;int sz=q.size();for(int i=0;i<sz;i++){auto [a,b] = q.front();q.pop();for(int k=0;k<4;k++){int x=a+dx[k],y=b+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&f[x][y]&&!vis[x][y]){if(x==ex&&y==ey) return step;q.push({x,y});vis[x][y]=true;}}}}return -1;}
};

为什么sort()改变比较对象是compare(),而priority_queue是直接compare:

class Solution {
public:int m,n;int cutOffTree(vector<vector<int>>& forest) {m=forest.size(),n=forest[0].size();//准备工作,找出砍树的顺序vector<pair<int,int>> trees;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(forest[i][j]>1) trees.push_back({i,j});}}    sort(trees.begin(),trees.end(),[&](const pair<int,int>& p1, const pair<int,int>& p2){return forest[p1.first][p1.second] < forest[p2.first][p2.second];});//按照顺序砍树int bx=0,by=0;int ret=0;for(auto& [a,b] : trees){int step = bfs(forest,bx,by,a,b);if(step==-1) return -1;ret+=step;bx=a,by=b;}return ret;}bool vis[51][51];int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};int bfs(vector<vector<int>>& f,int bx,int by,int ex,int ey){if(bx==ex&&by==ey) return 0;queue<pair<int,int>> q;memset(vis,0,sizeof(vis));q.push({bx,by});vis[bx][by]=true;int step=0;while(q.size()){step++;int sz=q.size();for(int i=0;i<sz;i++){auto [a,b] = q.front();q.pop();for(int k=0;k<4;k++){int x=a+dx[k],y=b+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&f[x][y]&&!vis[x][y]){if(x==ex&&y==ey) return step;q.push({x,y});vis[x][y]=true;}}}}return -1;}
};

总结:

可以看出这题的难度很大,但是如果慢慢细分,就会发现,只不过是前面的几个简单题拼接起来的罢了,所以还是要多写题,等刷题量上来了,真的会发现有巨大的进步~

三、多源 BFS

接下来进入多源BFS问题:

总结来说,就是将所有的单源点看成一个超级原点,全部一次性加入到队列内,然后在一个个出队列,同时带入下一层的起点;那么下面通过例题详细说明一下:

9. 01 矩阵(medium)

题目意思很简单,就是求当前1 的位置,离最近0的距离,把当前1该为该距离

解析:

解法一:

从每一个1 位置遍历,计算附近最近的0的位置,那么对于每一个1都来一次BFS,得到最近的距离,肯定会超时的,因为重复了很多操作;

解法二:正难则反;

这里在之前的floodfill算法里面有一题求海水灌溉的问题遇到过,也是最开始如果只是遍历每一个数,那么就太麻烦,重复性太高,但是这里,如果我们只考虑0的地方进行遍历,将所有0的源点视为一个“超级源点”,将所有的0进行入队列,然后开始往外扩展,0的所有下一层1,就全部修改为1;

1的所有下一层就全部修改为2;依次类推,直到整个二维数组被修改完成即可;

这里可以发现,在前面的一些题里面会出现要用bool vis[][]数组来判断当前位置有没有被访问过,sz来记录当前层的个数,step来记录总共要走的步数;这个题目唯一不一样的就是他有一个二维数组,那么我们就创建一个一样大,全是-1的二维数组,进行记录,访问一遍最初的二维数组,将所有0进行填充;

从每一个0开始往外扩充一层,进行修改扩充的值,就是dist[x][y]=dist[a][b]+1;这样就不需要专门拿step进行记录当前位置的值,并且只能进行修改为-1的位置的值,因为如果当前位置不是-1,就说明当前位置,早就被别的路径到达过,就不能再次进行修改了

class Solution {
public:int dx[4]={0,0,-1,1};int dy[4]={1,-1,0,0};vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {int m=mat.size(),n=mat[0].size();vector<vector<int>> dist(m,vector<int>(n,-1));queue<pair<int,int>> q;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(mat[i][j]==0){dist[i][j]=0;q.push({i,j});}}}while(q.size()){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&dist[x][y]==-1){q.push({x,y});dist[x][y]=dist[a][b]+1;}}}return dist;}
};

总结:

做完这一题真的收获满满,是一种新的解题思路,希望你也能学习到;

10. ⻜地的数量(medium)

题目很简单,这一题跟我们之前写的一题简直一模一样,就是求没有跟边界相邻的题目,计算有多大的面积

解析:

解法一:暴力搜索

遍历整个二维数组,遇到1就开始dfs或bfs,观察是否能走到边界,但是这种办法实在太暴力了,每次都会做很多重复的步骤,不能一次做完,时间复杂度太高,绝对会超时的。

解法二:正难则反

这题跟上一题很类似,但是跟我们之前做的有一题简直一模一样,这下简直手拿把掐!

那么就从最边界的每一个1为入口进行进入,进入后,开始进行BFS或DFS,这里我选择BFS,然后进行遍历,遇到的所有1,全部置为0,最后进行总结,总结出这个二维数组内还剩多少1,进行返回即可;

        for(int i=0;i<n;i++){if(grid[0][i]==1) bfs(grid,0,i);if(grid[m-1][i]==1) bfs(grid,m-1,i);}for(int i=0;i<m;i++){if(grid[i][0]==1) bfs(grid,i,0);if(grid[i][n-1]==1) bfs(grid,i,n-1);}

BFS:

void bfs(vector<vector<int>>& grid,int i,int j){queue<pair<int,int>> q;q.push({i,j});grid[i][j]=0;while(q.size()){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1){q.push({x,y});grid[x][y]=0;}}}}
最终代码:
class Solution {
public:int m,n;int dx[4]={0,0,-1,1};int dy[4]={1,-1,0,0};int numEnclaves(vector<vector<int>>& grid) {m=grid.size(),n=grid[0].size();for(int i=0;i<n;i++){if(grid[0][i]==1) bfs(grid,0,i);if(grid[m-1][i]==1) bfs(grid,m-1,i);}for(int i=0;i<m;i++){if(grid[i][0]==1) bfs(grid,i,0);if(grid[i][n-1]==1) bfs(grid,i,n-1);}int ret=0;for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(grid[i][j]==1) ret++;}}return ret;}void bfs(vector<vector<int>>& grid,int i,int j){queue<pair<int,int>> q;q.push({i,j});grid[i][j]=0;while(q.size()){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1){q.push({x,y});grid[x][y]=0;}}}}
};

总结:

这题做过原题,就不过多赘述,只是用不同的方法进行实现,我记得上传使用DFS实现的,都是一样的;

11. 地图中的最⾼点(medium)

这一题题目意思有点难理解,就是说,给定的水域1 最后要变成0,然后开始往外扩展,扩展一层就加一,跟之前写的一个题目很像;

解析:

那么就是遍历一次这个数组,创建一个dist[][]数组,初始化为-1,将1的位置全部填充成0,然后就将每一个0存入到队列q内,就开始进行BFS开始往外一层一层的扩展,这里就可以发现,这里跟上面几题可以完全不一样,只需要一个dist数组就能够完成bool[][] 和 对值的修改,只需要将

dist[x][y]=dist[a][b]+1 即可。

class Solution {
public:int dx[4]={0,0,-1,1};int dy[4]={1,-1,0,0};vector<vector<int>> highestPeak(vector<vector<int>>& isWater) {int m=isWater.size(),n=isWater[0].size();vector<vector<int>> dist(m,vector<int>(n,-1));queue<pair<int,int>> q;for(int i=0;i<m;i++)for(int j=0;j<n;j++)if(isWater[i][j]==1){dist[i][j]=0;q.push({i,j});}while(q.size()){auto [a,b] = q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&dist[x][y]==-1){dist[x][y]=dist[a][b]+1;q.push({x,y});}}}return dist;}
};

总结:

这题跟这个多源BFS第一题一模一样,可以很好的练手一下。

12. 地图分析(medium)

题目意思有点难理解,但是翻译过来就是求陆地向海洋进行扩展,返回最大距离

解析:

这题就是从陆地开始遍历BFS,但是做了这么多题,我相信只要设置一个dist就能跟前面代码一模一样,只需要将陆地设置为0即可,就可以向上面的题目一样BFS遍历,然后设置一个ret开始进行比较,只要取得最大层的值即可。

class Solution {
public:int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};int maxDistance(vector<vector<int>>& grid) {int m=grid.size(),n=grid[0].size();vector<vector<int>> dist(m,vector<int>(n,-1));queue<pair<int,int>> q;for(int i=0;i<m;i++)for(int j=0;j<n;j++)if(grid[i][j]==1){dist[i][j]=0;q.push({i,j});}int ret=0;while(q.size()){auto [a,b] = q.front();q.pop();for(int k=0;k<4;k++){int x=dx[k]+a;int y=dy[k]+b;if(x>=0&&x<m&&y>=0&&y<n&&dist[x][y]==-1){dist[x][y]=dist[a][b]+1;q.push({x,y});ret=max(ret,dist[x][y]);}}}return ret==0?-1:ret;}
};

总结:

写了这么多一模一样的题,主要目的还是希望能够记住这种BFS遍历,求关于层数最大值的方法,以后能够运用上。

四、BFS 解决拓扑排序:

拓扑排序简介:
1.

2.拓扑排序流程:

3.实现拓扑排序:

13. 课程表(medium)

题目意思有点难懂,就是要利用拓朴排序观察是否可以满足课程学习的条件

解析: 

可以看出题目意思就是课程学习的顺序 [1,0] 那么就是 要先学到0课程才能学习1课程,顺序就是
0 -> 1 

图论遍历就是分为邻接表和邻接矩阵两种方式,但是这里邻接矩阵只适合在数据密集的情况下,所以不考虑邻接矩阵,只考虑邻接表;

因为当使用hash表来当作邻接表的时候edges 存入所有点,来表示邻接表,因为一个点后面跟着一个数组,这个数组就是能表达出所有的邻接点

 unordered_map<int,vector<int>> edges;  //邻接表存图

in一维数组来标记每一个点的入度,如果当前点的入度为0,就说明可以被选用

2.建图:

将题目给定二维数组进行图的点的添加,将每一个节点进行入 邻接表(图)
后一个值使b,那么就将a添加到b内

        for(auto e : prerequisites)
        {
            int a=e[0],b=e[1];
            edges[b].push_back(a);
            in[a]++;
        }

3.开始拓扑排序,如果有环,就返回-1,说明失败了

成功的话就返回true,说明这个拓朴排序的顺序是可行的

创建一个队列 :queue<int> q;

1)把所有入度为0 的点加入到队列

        for(int i=0;i<n;i++)
        {
            if(in[i]==0) q.push(i);
        }

然后进行bfs宽搜,取队列一个入度为0 的点,删掉该点后,就开始访问该点后面跟着的数组的下一个点

        int t=q.front();q.pop();
            //干掉一个入度
            for(auto e : edges[t]) //就开始访问该点后面跟着的数组的下一个点
            {
                in[e]--;
                if(in[e]==0) q.push(e);
            }

因为要删除当前点,所以就要消灭一个下一个点的一个入度,并判断,如果这一个入度被删掉,这个点的入度为0,就又可以继续入队列了;这样就完成拓朴排序,直到队列内大小为0;

4.最后就是检查是否存在环

重新遍历一遍in数组,如果任然存在入度不为0的点,那么就说明存在环,直接返回false

class Solution {
public:bool canFinish(int n, vector<vector<int>>& prerequisites) {//1.准备工作unordered_map<int,vector<int>> edges;  //邻接表存图vector<int> in(n); //标记每一个定义的入度//2.建图for(auto e : prerequisites){int a=e[0],b=e[1];edges[b].push_back(a);in[a]++;}// 3.拓朴排序queue<int> q;//(1)把所有入度为0的点加入到队列中for(int i=0;i<n;i++){if(in[i]==0) q.push(i);}// (2)bfswhile(q.size()){int t=q.front();q.pop();//干掉一个入度for(auto e : edges[t]){in[e]--;if(in[e]==0) q.push(e);}}// 4.判断是否有环for(int i=0;i<n;i++)if(in[i]) return false;return true;}
};

总结:

题目意思很难,但总结出来就是拓朴排序,只要自己深入思考一下下,我相信还是能解决的,加油!~

14. 课程表II(medium)

跟上一题一模一样,题目意思有点难懂,就是要利用拓朴排序观察是否可以满足课程学习的条件

解析: 

可以看出题目意思就是课程学习的顺序 [1,0] 那么就是 要先学到0课程才能学习1课程,顺序就是
0 -> 1 

图论遍历就是分为邻接表和邻接矩阵两种方式,但是这里邻接矩阵只适合在数据密集的情况下,所以不考虑邻接矩阵,只考虑邻接表;

因为当使用hash表来当作邻接表的时候edges 存入所有点,来表示邻接表,因为一个点后面跟着一个数组,这个数组就是能表达出所有的邻接点

 unordered_map<int,vector<int>> edges;  //邻接表存图

in一维数组来标记每一个点的入度,如果当前点的入度为0,就说明可以被选用

2.建图:

将题目给定二维数组进行图的点的添加,将每一个节点进行入 邻接表(图)
后一个值使b,那么就将a添加到b内

        for(auto e : prerequisites)
        {
            int a=e[0],b=e[1];
            edges[b].push_back(a);
            in[a]++;
        }

3.开始拓扑排序,如果有环,就返回-1,说明失败了

成功的话就返回true,说明这个拓朴排序的顺序是可行的

创建一个队列 :queue<int> q;

1)把所有入度为0 的点加入到队列

        for(int i=0;i<n;i++)
        {
            if(in[i]==0) q.push(i);
        }

然后进行bfs宽搜,取队列一个入度为0 的点,删掉该点后,就开始访问该点后面跟着的数组的下一个点

        int t=q.front();q.pop();
            //干掉一个入度
            for(auto e : edges[t]) //就开始访问该点后面跟着的数组的下一个点
            {
                in[e]--;
                if(in[e]==0) q.push(e);
            }

因为要删除当前点,所以就要消灭一个下一个点的一个入度,并判断,如果这一个入度被删掉,这个点的入度为0,就又可以继续入队列了;这样就完成拓朴排序,直到队列内大小为0;

4.最后就是检查是否存在环

重新遍历一遍in数组,如果任然存在入度不为0的点,那么就说明存在环,直接返回false

class Solution {
public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {//1.准备工作vector<vector<int>> edges(numCourses); //邻接表存图vector<int> in(numCourses); //储存每一个点的入度// 2.建图for(auto v : prerequisites){int a=v[0],b=v[1]; //b -> aedges[b].push_back(a);in[a]++;}//3.拓朴排序queue<int> q;vector<int> ret;for(int i=0;i<numCourses;i++){if(in[i]==0) q.push(i);}while(q.size()){int t=q.front();q.pop();ret.push_back(t);for(auto a : edges[t]){in[a]--;if(in[a]==0) q.push(a);}}// 4.判断if(ret.size()==numCourses) return ret;return {};}
};

总结:

题目意思很难,但总结出来就是拓朴排序,只要自己深入思考一下下,我相信还是能解决的,加油!~

15. ⽕星词典(hard)

题目意思有点难理解,就是根据新的语言字符顺序将字符进行排序;

解析:

算法原理介绍:

 

class Solution {
public:unordered_map<char,unordered_set<char>> edges; //邻接表来存储图unordered_map<char,int> in; //统计入度bool cheak; //处理边界情况string alienOrder(vector<string>& words) {//1.建图 + 初始化入度哈希表for(auto s : words)for(auto ch : s)in[ch]=0;int n=words.size();for(int i=0;i<n;i++)for(int j=i+1;j<n;j++){add(words[i],words[j]);if(cheak) return "";}//2.拓朴排序queue<char> q;for(auto [a,b] : in) if(b==0) q.push(a);string ret;while(q.size()){char t=q.front();q.pop();ret+=t;for(auto ch : edges[t]) if(--in[ch]==0) q.push(ch);}// 3.判断for(auto [a,b] : in) if(b != 0) return "";return ret;}void add(string s1,string s2){int n=min(s1.size(),s2.size());int i=0;for(;i<n;i++){if(s1[i] != s2[i]){char a=s1[i],b=s2[i]; //a -> bif(!edges.count(a) || !edges[a].count(b)){edges[a].insert(b);in[b]++;}break;}}if(i==s2.size() && i<s1.size()) cheak = true;}
};

总结:

除了本章的拓扑排序,其他的小专题应该没什么难度,只是拓扑排序要另外了解其他算法思想的题目,成本有一点点高,总体来说,BFS还是比较简单的。

总结一下吧~总结不易,本章对我的学习进步收获很大,希望对你也是~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/59052.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NVR小程序接入平台/设备EasyNVR多个NVR同时管理视频监控新选择

在数字化转型的浪潮中&#xff0c;视频监控作为安防领域的核心组成部分&#xff0c;正经历着前所未有的技术革新。随着技术的不断进步和应用场景的不断拓展&#xff0c;视频监控系统的兼容性、稳定性以及安全性成为了用户关注的焦点。NVR小程序接入平台/设备EasyNVR&#xff0c…

leetcode71:简化路径

给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为 更加简洁的规范路径。 在 Unix 风格的文件系统中规则如下&#xff1a; 一个点 . 表示当前目录本身。此外&#xff0c;两个点 ..…

vscode 创建 vue 项目时,配置文件为什么收缩到一起展示了?

一、前言 今天用 vue 官方脚手架创建工程&#xff0c;然后通过 vscode 打开项目发现&#xff0c;配置文件都被收缩在一起了。就像下面这样 这有点反直觉&#xff0c;他们应该是在同一层级下的&#xff0c;怎么会这样&#xff0c;有点好奇&#xff0c;但是打开资源管理查看&…

大学适合学C语言还是Python?

在大学学习编程时&#xff0c;选择C语言还是Python&#xff0c;这主要取决于你的学习目标、专业需求以及个人兴趣。以下是对两种语言的详细比较&#xff0c;帮助你做出更明智的选择&#xff1a; C语言 优点&#xff1a; 底层编程&#xff1a;C语言是一种底层编程语言&#x…

yolov8涨点系列之优化器替换

文章目录 优化器替换的重要性加速收敛速度提高模型精度增强模型的泛化能力适应不同的数据集和任务特点 优化器替换步骤(1)准备代码&#xff1a;(2)导入优化器(3)替换优化器 本文将以替换Lion为优化器的方式展示如何对Ultrayluic的yolov8进行优化器替换。 优化器替换的重要性 加…

Vue 学习随笔系列十三 -- ElementUI 表格合并单元格

ElementUI 表格合并单元格 文章目录 ElementUI 表格合并单元格[TOC](文章目录)一、表头合并二、单元格合并1、示例代码2、示例效果 一、表头合并 参考&#xff1a; https://www.jianshu.com/p/2befeb356a31 二、单元格合并 1、示例代码 <template><div><el-…

C++ -- 模板进阶

非模板类型参数 模板参数分为类型形参与非类型形参。类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class 或 typename之类的参数类型名称。非类型形参&#xff1a;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中将该参数当成常量来使用。…

今日 AI 简报|零样本视频生成、移动端轻量语言模型、自动驾驶多模态模型等前沿 AI 技术集中亮相

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

如何监控员工上网行为?实现精准监控员工上网行为的5个妙招分享!(企业:稳了!)

如何监控员工上网行为&#xff1f; 员工上班时的"摸鱼"行为员工上网行为&#xff08;做与工作无关的活动&#xff0c;如浏览社交媒体、游戏、网购等&#xff09;&#xff0c;不仅影响工作效率&#xff0c;还可能破坏团队氛围&#xff0c;阻碍企业发展。 那么&#…

Allegro: 开源的高级视频生成模型

我们很高兴地宣布 Allegro 的开源发布&#xff0c;这是 Rhymes AI 先进的文本到视频模型。Allegro 是一款功能强大的人工智能工具&#xff0c;能将简单的文字提示转化为高质量的视频短片&#xff0c;为人工智能生成视频领域的创作者、开发者和研究人员开辟了新的可能性。我们希…

<项目代码>YOLOv8 猫狗识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

「Mac畅玩鸿蒙与硬件21」鸿蒙UI组件篇11 - Canvas 组件的静态进阶应用

在鸿蒙应用开发中,Canvas 组件不仅用于基础绘图,还提供了处理复杂路径和渐变效果的多种手段,帮助开发者实现精美的静态图形。本篇将介绍如何在 Canvas 中绘制复杂路径、创建渐变填充效果。 关键词 Canvas 组件复杂路径绘制渐变填充一、Canvas 的复杂路径绘制 Canvas 提供了…

Java 用户随机选择导入ZIP文件,解压内部word模板并入库,Windows/可视化Linux系统某麒麟国防系统...均可适配

1.效果 压缩包内部文件 2.依赖 <!--支持Zip--><dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>2.11.5</version></dependency>总之是要File类变MultipartFile类型的 好像是…

mint-ui Picker 显示异常

mint-ui Picker 显示异常 现象 最近一个老项目页面显示异常&#xff0c;使用mint-ui Picker显示异常,直接显示成了 数据对象&#xff0c;而不是具体travelName 字段 组件 mint-ui Picker 使用方式(vue方式) // template <mt-picker :slots"slots" value-key…

【重生之我要苦学C语言】深入理解指针2

深入理解指针2 const修饰指针 当const修饰变量时&#xff0c;是无法更该该变量的值的 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {const int a 10;//const常属性&#xff0c;不能改变的属性a 1;printf("%d\n", a);return 0; }报错&…

半参数模型

4. 半参数模型 (Semi-parametric Model) 半参数模型结合了参数化和非参数化的方法。可以在整体上采用线性回归&#xff0c;但在局部允许非线性变化。这样做的目的是在保持模型的线性解释性的同时&#xff0c;捕捉细微的弧度趋势。 例如&#xff0c;可以定义&#xff1a; y …

LInux系统编程(二)操作系统和进程

目录 一、前言&#xff1a;冯诺依曼体系结构 1、图中各个单元的介绍 2、值得注意的几点 二、操作系统 1、操作系统分层图 2、小总结 三、 进程&#xff08;重点&#xff09; 1、进程的基本概念 2、存放进程信息的数据结构——PCB&#xff08;Linux 下称作 task_struct…

加法电路和减法电路

一、加法电路 下边为加法电路的拓扑结构 加法电路作用1: 直流量叠加 如上图仿真所示,利用放大器LM324AD进行加法电路的仿真,输入为直流+1V和直流+2V,经过加法运算,根据上边Uo的计算公式进行计算,可得Uo=-3V,和仿真结果保持一致。如下图所示。 加法电路作用2: 信号叠加…

8. 数据结构——邻接表、邻接矩阵的基本操作

一、邻接表 1. 内容 2. 实现代码(直接可以复制使用) //邻接表的相关操作 #include<bits/stdc.h> #define MVnum 100 #define OK 1 #define ERROR -1 using namespace std;typedef int Status; typedef char VerTexType; //假设顶点的数据类型为char typedef int ArcT…

自动化研磨领域的革新者:半自动与自动自磨机的技术突破

据QYResearch调研团队最新报告“全球半自动和自动自磨机市场报告2023-2029”显示&#xff0c;预计2029年全球半自动和自动自磨机市场规模将达到5.3亿美元&#xff0c;未来几年年复合增长率CAGR为3.5%。 图00001. 半自动和自动自磨机&#xff0c;全球市场总体规模 如上图表/数据…