目录
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)
解析:
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
总结来说,就是将所有的单源点看成一个超级原点,全部一次性加入到队列内,然后在一个个出队列,同时带入下一层的起点;那么下面通过例题详细说明一下:
9. 01 矩阵(medium)
解析:
解法一:
从每一个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)
解析:
那么就是遍历一次这个数组,创建一个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 解决拓扑排序:
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还是比较简单的。