💁♂️个人主页:进击的荆棘
👇作者其它专栏:
目录
1.BFS解决FloodFill算法
2.BFS解决最短路问题
3.多源BFS
4.BFS解决拓扑排序
1.BFS解决FloodFill算法
1.1图像渲染
算法思路:
可以利用【深搜】或【宽搜】,遍历到与该点相连的所有【像素值相同的点】,然后将其修改成指定的像素。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
//将需要修改的像素值提前保存
int prev=image[sr][sc];
//若起始位置值已经与color相同,直接返回
if(image[sr][sc]==color) return image;
int m=image.size(),n=image[0].size();
queue<pair<int,int>> q;
q.push({sr,sc});
image[sr][sc]=color;
while(q.size()){
auto [a,b]=q.front();
q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==prev){
image[x][y]=color;
q.push({x,y});
}
}
}
return image;
}
};
1.2岛屿数量
算法思路:
遍历整个矩阵,每次找到【一块陆地】时:
●说明找到【一个岛屿】,记录到最终结果ret里;
●并且将这个陆地相连的所有陆地,即这块【岛屿】,全部变成【海洋】。这样的话,下次遍历到这块岛屿时,它【已经是海洋】了,不会影响最终结果。(有时不能修改原数组,也可以用一个bool数组标记遍历过的位置)
●其中【变成海洋】的操作,可以利用【深搜】和【宽搜】解决,其实就是 1.1图像渲染
这样,当遍历完全部矩阵式,ret存的就是最终结果。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
bool check[301][301];
int m,n;
public:
int numIslands(vector<vector<char>>& grid) {
m=grid.size(),n=grid[0].size();
int ret=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'&&!check[i][j]){
ret++;
bfs(grid,i,j);
}
}
}
return ret;
}
void bfs(vector<vector<char>>& grid,int i,int j){
queue<pair<int,int>> q;
q.push({i,j});
check[i][j]=true;
while(q.size()){
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&&!check[x][y]&&grid[x][y]=='1'){
q.push({x,y});
check[x][y]=true;
}
}
}
}
};
1.3岛屿的最大面积
算法思路:
●遍历整个矩阵,每当遇到一块土地时,用【深搜】或【宽搜】将与这块土地相连的【整个岛屿】的面积计算出来。
●然后再搜索得到的【所有岛屿面积】求一个【最大值】。
●在搜索过程中,为【防止搜到重复的土地】:
▢可以开一个同等规模的【布尔数组】,标记一下这个位置是否已经被访问过;
▢也可以将原始矩阵的1修改为0,但这样操作会修改原始矩阵。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
bool check[51][51];
int m,n;
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
m=grid.size(),n=grid[0].size();
int ret=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1&&!check[i][j])
ret=max(bfs(grid,i,j),ret);
}
}
return ret;
}
int bfs(vector<vector<int>>& grid,int i,int j){
queue<pair<int,int>> q;
int ret=1;
q.push({i,j});
check[i][j]=true;
while(q.size()){
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&&grid[x][y]==1&&!check[x][y]){
q.push({x,y});
check[x][y]=true;
ret++;
}
}
}
return ret;
}
};
1.4被围绕的区域
算法思路:
正难则反。
可以先利用bfs将与边缘相连的'O'区域做标记,然后重新遍历矩阵,将没有标记过的'O'修改为'X'
cpp
class Solution {
int m,n;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
void solve(vector<vector<char>>& board) {
m=board.size(),n=board[0].size();
for(int i=0;i<n;i++){
if(board[0][i]=='O') bfs(board,0,i);
if(board[m-1][i]=='O') bfs(board,m-1,i);
}
for(int i=0;i<m;i++){
if(board[i][0]=='O') bfs(board,i,0);
if(board[i][n-1]=='O') bfs(board,i,n-1);
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(board[i][j]=='O') board[i][j]='X';
else if(board[i][j]=='.') board[i][j]='O';
}
}
}
void bfs(vector<vector<char>>& board,int i,int j){
queue<pair<int,int>> q;
q.push({i,j});
board[i][j]='.';
while(q.size()){
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&&board[x][y]=='O'){
q.push({x,y});
board[x][y]='.';
}
}
}
}
};
2.BFS解决最短路问题
2.1迷宫中离入口最近的出口
算法思路:
利用层序遍历来解决迷宫问题,是最经典的做法。
可以从起点开始层序遍历,并在遍历的过程中记录当前遍历的层数。这样就能在找出出口的时候,得到起点到出口的最短距离。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
int ret=0;
int m=maze.size(),n=maze[0].size();
bool check[m][n];
memset(check,0,sizeof check);
queue<pair<int,int>> q;
q.push({entrance[0],entrance[1]});
check[entrance[0]][entrance[1]]=true;
while(q.size()){
int cnt=q.size();
ret++;
while(cnt--){
auto [a,b]=q.front();
q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
if(x>=0&&x<m&&y>=0&&y<n&&maze[x][y]=='.'&&!check[x][y]){
if(x==0||x==m-1||y==0||y==n-1) return ret;
q.push({x,y});
check[x][y]=true;
}
}
}
}
return -1;
}
};
2.2最小基因变化
算法思路:
若将【每次字符串的变化】抽象为图中的【两个顶点和一条边】的话,问题就变成了【边权为1的最短路问题】。
从起始的字符串开始,来一次bfs即可。
cpp
class Solution {
public:
int minMutation(string startGene, string endGene, vector<string>& bank) {
unordered_set<string> vis;//标记已经存在的状态
unordered_set<string> hash(bank.begin(),bank.end());//存储bank中的字符串
string change="ACGT";
if(startGene==endGene) return 0;
if(!hash.count(endGene)) return -1;
queue<string> q;
q.push(startGene);
int ret=0;
while(q.size()){
ret++;
int n=q.size();
while(n--){
string t=q.front();
q.pop();
for(int i=0;i<8;i++){
string tmp=t;
for(int j=0;j<4;j++){
tmp[i]=change[j];
if(tmp==endGene) return ret;
if(hash.count(tmp)&&!vis.count(tmp)){
vis.insert(tmp);
q.push(tmp);
}
}
}
}
}
return -1;
}
};
2.3单词接龙
算法思路:
与 2.2最小基因变化 相似。
cpp
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> vis;
unordered_set<string> hash(wordList.begin(),wordList.end());
if(!hash.count(endWord)) return 0;
queue<string> q;
q.push(beginWord);
int ret=1;
while(q.size()){
ret++;
int n=q.size();
while(n--){
string t=q.front();
q.pop();
for(int i=0;i<t.size();i++){
string tmp=t;
for(char j='a';j<='z';j++){
tmp[i]=j;
if(tmp==endWord) return ret;
if(hash.count(tmp)&&!vis.count(tmp)){
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
return 0;
}
};
2.4为高尔夫比赛砍树
算法思路:
a.先找出砍树的顺序;
b.然后按照砍树的顺序,一个一个的用bfs求出最短路即可。
cpp
class Solution {
int m,n;
public:
//相当于若干个迷宫问题,从某点走的某点的最短路径
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>>& forest,int bx,int by,int ex,int ey){
if(bx==ex&&by==ey) return 0;
//每次将之前的干扰清除
memset(vis,0,sizeof vis);
queue<pair<int,int>> q;
q.push({bx,by});
vis[bx][by]=true;
int step=0;
while(q.size()){
step++;
int sz=q.size();
while(sz--){
auto [a,b]=q.front();
q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
if(x>=0&&x<m&&y>=0&&y<n&&forest[x][y]&&!vis[x][y]){
if(x==ex&&y==ey) return step;
q.push({x,y});
vis[x][y]=true;
}
}
}
}
return -1;
}
};
3.多源BFS
3.101 矩阵
算法思路:
对于求的结果,有两种方式:
●第一种方式:从每一个1开始,然后通过层序遍历找到离它最近的0。
这一种方式,会以所有的1起点,来一次层序遍历,势必会遍历到很多重复的点。并且若矩阵中只有一个0的话,每次层序遍历需遍历很多层,时间复杂度会很高。
●换一种方式:从0开始层序遍历,并且记录遍历的层数。当第一次碰到1的时候,当前的层数就是这个1离0的最短距离。
这一种方式,我们在遍历的时候标记一下处理过的1,能够做到只用遍历整个矩阵一次,就能得到最终结果。
但是这里有一个问题,0是有很多个的,怎么才能保证遇到的1距离这一个0是最近的呢?
其实很简单,我们可以先把所有的0都放在队列中,把它们当成一个整体(一个超级源点),每次把当前队列里面的所有元素向外扩展一次。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
//寻找多源起点,正难则反,寻找0再去更新数组中的值更容易
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int m=mat.size(),n=mat[0].size();
//dist[i][j]==-1,表示该位置未遍历过
//dist[i][j]!=-1,最短距离
vector<vector<int>> dist(m,vector<int>(n,-1));
queue<pair<int,int>> q;
//寻找所有的0,并添加进队列+数组
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});
}
}
}
//开始bfs
while(q.size()){
auto [a,b]=q.front();
q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
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;
}
};
3.2飞地的数量
算法思路:
正难则反:
从边上的1开始搜索,把与边上1相连的连通区域全部标记一下;
然后再遍历一般矩阵,看看哪些位置的1没有被标记即可。
标记的时候,可以用【多源bfs】解决。
cpp
class Solution {
int m,n;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
int numEnclaves(vector<vector<int>>& grid) {
m=grid.size(),n=grid[0].size();
vector<vector<bool>> vis(m,vector<bool>(n));
queue<pair<int,int>> q;
//1.将边界上的1加入队列
for(int i=0;i<m;i++){
if(grid[i][0]==1) q.push({i,0});
if(grid[i][n-1]==1) q.push({i,n-1});
}
for(int j=0;j<n;j++){
if(grid[0][j]==1) q.push({0,j});
if(grid[m-1][j]==1) q.push({m-1,j});
}
//2.bfs
while(q.size()){
auto [a,b]=q.front();
q.pop();
vis[a][b]=true;
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1&&!vis[x][y]){
q.push({x,y});
vis[x][y]=true;
}
}
}
//3.统计结果
int ret=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1&&!vis[i][j]) ret++;
}
}
return ret;
}
};
3.3地图中的最高点
算法思路:
与 3.101 矩阵 思路相同,直接用多源bfs解决即可。
cpp
class Solution {
int m,n;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
vector<vector<int>> highestPeak(vector<vector<int>>& isWater) {
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){
q.push({i,j});
dist[i][j]=0;
}
}
}
while(q.size()){
auto [a,b]=q.front();q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
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;
}
};
3.4地图分析
算法思路:
01矩阵的变形题,直接用多源bfs解决即可。
cpp
class Solution {
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
public:
//把1当作超级源点,向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=-1;
while(q.size()){
auto [a,b]=q.front();q.pop();
for(int i=0;i<4;i++){
int x=a+dx[i],y=b+dy[i];
if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==0&&dist[x][y]==-1){
dist[x][y]=dist[a][b]+1;
ret=max(ret,dist[x][y]);
q.push({x,y});
}
}
}
return ret;
}
};
4.BFS解决拓扑排序
4.1课程表
算法思路:
原问题可以转化成一个拓扑排序问题。
用BFS解决拓扑排序即可。
拓扑排序流程:
a.将所有入度位0的点加入到队列中;
b.当队列不空时,一直循环:
i.取出队头元素;
ii.将与队头元素相连的顶点的入度-1;
iii.然后判断是否减成0,若为0,就加入到队列中。
cpp
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//1.准备工作
unordered_map<int,vector<int>> edges;//用邻接表建图
vector<int> in(numCourses);//记录每个点的入度
//2.建图
for(auto e:prerequisites){
int a=e[0],b=e[1];//b -> a
edges[b].push_back(a);
in[a]++;
}
queue<int> q;
//将所有入度为0的点加入队列
for(int i=0;i<numCourses;i++){
if(in[i]==0){
q.push(i);
}
}
//bfs
while(q.size()){
int t=q.front();q.pop();
//修改相连的边
for(auto a:edges[t]){
in[a]--;
if(in[a]==0){
q.push(a);
}
}
}
//判断是否有环
for(int i=0;i<numCourses;i++){
if(in[i]) return false;
}
return true;
}
};
4.2课程表 II
算法思路:
与 4.1课程表 思路相同
cpp
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
//unordered_map<int,vector<int>> edges;
//当数据稀疏时,可以用二维数组来建邻接表
//1.准备工作
vector<vector<int>> edges(numCourses);//用二维数组来建邻接表
vector<int> in(numCourses);//记录每个点的入度
//2.建图
for(auto e:prerequisites){
int a=e[0],b=e[1];
edges[b].push_back(a);
in[a]++;
}
//拓扑排序
queue<int> q;
for(int i=0;i<numCourses;i++){
if(in[i]==0) q.push(i);
}
vector<int> ret;
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);
}
}
}
if(ret.size()==numCourses) return ret;
else return {};
}
};
4.3火星词典
算法思路:
将题意搞清楚后,这道题就变成了判断有向图是否有环,可以用拓扑排序解决。
如何搜集信息(如何建图):
a.两层for循环枚举出所有的两个字符串的组合;
b.然后利用指针,根据字典序规则找出信息。
cpp
class Solution {
unordered_map<char,unordered_set<char>> edges;//创建邻接表
unordered_map<char,int> in;//记录每个点的入度
bool check;
public:
string alienOrder(vector<string>& words) {
//将入度初始化
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(check) return "";
}
}
//将入度为0的加入队列
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);
}
}
}
//判断
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;
while(i<n){
if(s1[i]!=s2[i]){
char a=s1[i],b=s2[i];//a -> b
if(!edges.count(a)||!edges[a].count(b)){
edges[a].insert(b);
in[b]++;
}
break;
}
i++;
}
//特例
if(i==s2.size()&&i<s1.size()) check=true;
}
};