LeetCode-BFS问题
1.Floodfill问题
1.图像渲染问题 [https://leetcode.cn/problems/flood-fill/description/](https://leetcode.cn/problems/flood-fill/description/)class Solution {
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
//可以借助另一个数组来完成
int rows=image.length;
int cols=image[0].length;
//默认为一个false
boolean nums2[][]=new boolean[rows][cols];
int ret=image[sr][sc];
image[sr][sc]=color;
nums2[sr][sc]=true;
int[] position=new int[]{sr,sc};
Queue<int [] > queue=new LinkedList<>();
queue.offer(position);
while(!queue.isEmpty()) {
int size=queue.size();
while(size>0) {
int [] nums1=queue.poll();
int row=nums1[0];
int col=nums1[1];
if(row-1>=0 && image[row-1][col] ==ret && nums2[row-1][col]==false) {
queue.offer(new int[]{row-1,col});
nums2[row-1][col]=true;
}
if(col-1>=0 && image[row][col-1] ==ret && nums2[row][col-1]==false) {
queue.offer(new int[]{row,col-1});
nums2[row][col-1]=true;
}
if(col+1<=cols-1 && image[row][col+1] ==ret && nums2[row][col+1]==false) {
queue.offer(new int[]{row,col+1});
nums2[row][col+1]=true;
}
if(row+1<=rows-1 && image[row+1][col] ==ret && nums2[row+1][col]==false) {
queue.offer(new int[]{row+1,col});
nums2[row+1][col]=true;
}
image[row][col]=color;
size--;
}
}
return image;
}
}
从一个位置开始将上下左右的连接起来的渲染成color。当我们拿到这道题的时候,可能想不到bfs也就是层序遍历。如果我仔细相想一下如果从一个点开始向周围蔓延,这有可能会想到层序遍历。
从(1,1)这个位置开始,这个位置值设为target,开始向上下左右四个方向遍历,如果值跟target相同就加入队列。但是问题也就来了 ,如果当我们遍历到左边时候,按照以上的说法仍会将右边的值加入到队列中。如下图所示
所以这个时候就初始化一个跟原有的数组一样大,来记录那些地方我们已经走过了,也就是代码上面的nums2数组,当我们加入到队列的时候,设置为true。队列中为一个二元组,是这个元素的横坐标和纵坐标。
2.岛屿数量 https://leetcode.cn/problems/number-of-islands/
class Solution {
public int numIslands(char[][] grid) {
char ret='1';//代表的是岛屿的标志
boolean [][] nums2=new boolean[300][300];
int ans=0;
Queue<int [] >queue=new LinkedList<>();
int rows=grid.length;
int cols=grid[0].length;
for(int i=0;i<rows;i++) {
for(int j=0;j<cols;j++) {
if(grid[i][j] == ret && nums2[i][j]==false) {
queue.offer(new int []{i,j});
nums2[i][j]=true;
while(!queue.isEmpty()) {
int size=queue.size();
while(size>0) {
int [] nums1=queue.poll();
int row=nums1[0];
int col=nums1[1];
if(row-1>=0 && grid[row-1][col] ==ret && nums2[row-1][col]==false) {
queue.offer(new int[]{row-1,col});
nums2[row-1][col]=true;
}
if(col-1>=0 && grid[row][col-1] ==ret && nums2[row][col-1]==false) {
queue.offer(new int[]{row,col-1});
nums2[row][col-1]=true;
}
if(col+1<=cols-1 && grid[row][col+1] ==ret && nums2[row][col+1]==false) {
queue.offer(new int[]{row,col+1});
nums2[row][col+1]=true;
}
if(row+1<=rows-1 && grid[row+1][col] ==ret && nums2[row+1][col]==false) {
queue.offer(new int[]{row+1,col});
nums2[row+1][col]=true;
}
size--;
}
}
ans++;
}
}
}
return ans;
}
}
这道题是求连起来的岛屿的数量,也就是层序遍历了几次,统计层序遍历的结果。
这一段代码有一些冗余以及不美观,下面的题目会使用两个数组的形式来控制这个上下左右方向的走向。
3.岛屿的最大面积 https://leetcode.cn/problems/max-area-of-island/description/
class Solution {
public int maxAreaOfIsland(int[][] grid) {
//感觉还是一样的魔板
int rows=grid.length;
int cols=grid[0].length;
int ret=1;
//用来记录岛屿的面积
int ans=0;
Queue<int [] >queue=new LinkedList<>();
boolean [][] nums2=new boolean [50][50];
for(int i=0;i<rows;i++) {
for(int j=0;j<cols;j++) {
if(grid[i][j]==ret && nums2[i][j]==false) {
queue.offer(new int[]{i,j});
nums2[i][j]=true;
int count=0;
while(!queue.isEmpty()) {
int size=queue.size();
count+=size;
while(size>0) {
int [] nums1=queue.poll();
int row=nums1[0];
int col=nums1[1];
if(row-1>=0 && grid[row-1][col] ==ret && nums2[row-1][col]==false) {
queue.offer(new int[]{row-1,col});
nums2[row-1][col]=true;
}
if(col-1>=0 && grid[row][col-1] ==ret && nums2[row][col-1]==false) {
queue.offer(new int[]{row,col-1});
nums2[row][col-1]=true;
}
if(col+1<=cols-1 && grid[row][col+1] ==ret && nums2[row][col+1]==false) {
queue.offer(new int[]{row,col+1});
nums2[row][col+1]=true;
}
if(row+1<=rows-1 && grid[row+1][col] ==ret && nums2[row+1][col]==false) {
queue.offer(new int[]{row+1,col});
nums2[row+1][col]=true;
}
size--;
}
}
ans=Math.max(ans,count);
}
}
}
return ans;
}
}
统计出岛屿的最大面积,就是在统计出岛屿的时候,再计算一步岛屿的面积,由于一个格子的面积是1。当我们进入到层序遍历的时候,使用count变量依次加上队列中这一层的格子数,注意一个岛屿一般都是有很多层的。进入到下一个岛屿遍历的时候,重新初始化count变量。
4.被围绕的区域 https://leetcode.cn/problems/surrounded-regions/description/
class Solution {
int [] dx={1,-1,0,0};
int [] dy={0,0,1,-1};
public void solve(char[][] grid) {
//感觉还是一样的魔板
//先遍历周围的模块
char tmp='A';
int rows=grid.length;
if(rows==0) {
return;
}
int cols=grid[0].length;
Queue<int [] > queue=new LinkedList<>();
for(int i =0;i<cols;i++) {
if(grid[0][i]=='O') {
queue.offer(new int[] {0,i});
grid[0][i]=tmp;
}
if(grid[rows-1][i]=='O') {
queue.offer(new int[]{rows-1,i});
grid[rows-1][i]=tmp;
}
}
for(int i=1;i<rows-1;i++) {
if(grid[i][0]=='O') {
queue.offer(new int []{i,0});
grid[i][0]=tmp;
}
if(grid[i][cols-1]=='O') {
queue.offer(new int[] {i,cols-1});
grid[i][cols-1]=tmp;
}
}
while(!queue.isEmpty()) {
int [] nums1= queue.poll();
int row=nums1[0];
int col=nums1[1];
for(int k=0;k<4;k++) {
int a=row+dx[k],b=col+dy[k];
if(a>=0 && a<rows && b>=0 && b<cols && grid[a][b]=='O' ) {
queue.offer(new int []{a,b});
grid[a][b]=tmp;
}
}
}
for(int i =0;i<rows;i++) {
for(int j =0;j<cols;j++) {
if(grid[i][j]==tmp) {
grid[i][j]='O';
}else if(grid[i][j]=='O') {
grid[i][j]='X';
}
}
}
}
}
当我们还是按照上面题目的思想来解决这道题目的话,是行不通的。我们仔细观察题目只要区域中包含有外围的格子他就不是被包围的区域,于是我们先遍历周围的格子将周围相连的格子变成tmp,经过bfs之后再进行遍历数组,将tmp的变成’0’,将’0’变成’X’,正难则反,先将不被包围的区域变成一个tmp,再将被包围的区域变成一个’X’。
2.解决最短路问题
1.迷宫中离入口最近的出口 [https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/description/](https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/description/)class Solution {
public int nearestExit(char[][] nums, int[] entrance) {
// +表示墙,.表示的是一个可走的路
int rows=nums.length;
int cols=nums[0].length;
int [] dx=new int[] {0,0,1,-1};
int [] dy=new int[] {1,-1,0,0};
boolean [][] visit=new boolean[rows][cols];
int a=entrance[0],b=entrance[1];
Queue<int[] > queue=new LinkedList<>();
queue.offer(new int[]{a,b});
visit[a][b]=true;
int ans=0;
while(!queue.isEmpty()) {
ans++;
int size=queue.size();
for(int i =0;i<size;i++) {
int [] tmp=queue.poll();
int row=tmp[0],col=tmp[1];
for(int j=0;j<4;j++) {
int x=row+dx[j],y=col+dy[j];
if(x>=0 && y>=0 && x<rows && y<cols && !visit[x][y] && nums[x][y]=='.') {
if(x==0 || x==rows-1 || y==0 || y==cols-1) {
return ans;
}
queue.offer(new int[] {x,y});
visit[x][y]=true;
}
}
}
}
return -1;
}
}
这里研究的最短路的问题边权都是为1的最短路问题。这里为什么能用bfs来解决,如下图说明。
以红点出发有四个方向可以走,上下左右四个方向。当然这里我们并没有实际走,只是在逻辑上走。
其实这个过程是将所有的结果给枚举出来,结果就是进行几次层序遍历。当我们碰到一个节点为’.'的时候,此时位置正好为边缘的时候,我们将结果返回。为什么进行几层层序遍历就是最小值呢,是因为我们在一层一层进行遍历,遍历的同时看是否到达出口,如果提前有出口,我们也就提前返回了。
2.最小基因变化 https://leetcode.cn/problems/minimum-genetic-mutation/description/
class Solution {
public int minMutation(String start, String end, String[] bank) {
if(bank.length==0 || bank==null) {
return -1;
}
if(start==end) {
return 0;
}
// 也就是说当我们在进行变化的时候,只有当是基因库中的才有效
Queue<String> queue=new LinkedList<>();
//这里使用哈希表来进行记录已经遍历过的
Map<String,Boolean> hash=new HashMap<>();
for(String str:bank) {
hash.put(str,false);
}
int ans=0;
char[] nums={'A','C','G','T'};
queue.offer(start);
while(!queue.isEmpty()) {
int size=queue.size();
ans++;
while(size>0) {
String tmp=queue.poll();
for(int i=0;i<tmp.length();i++) {
// 每个字符就会对应出4个结果
for(int j =0;j<4;j++) {
// 这里无法在原有的字符串上面进行更改值
String ret= tmp.substring(0,i)+nums[j]+tmp.substring(i+1,8);
if(ret.equals(end) && hash.containsKey(ret)) {
return ans;
}
if(hash.containsKey(ret) && hash.get(ret)==false) {
queue.offer(ret);
hash.put(ret,true);
}
}
}
size--;
}
}
return -1;
}
}
首先,先创建出一个哈希表用来标志这个基因是否到达过。每个基因的就是一个字符串都是等长的,在每个位置进行变换,于是就有了String ret= tmp.substring(0,i)+nums[j]+tmp.substring(i+1,8);这一行代码。剩下的就是跟之前是一样的,这里的变化必须是基因库的。
3.单词接龙 https://leetcode.cn/problems/om3reC/description/
class Solution {
public int ladderLength(String start, String end, List<String> word) {
Map<String,Boolean> hash=new HashMap<>();
int len=start.length();
int ans=1;
for(String str : word) {
hash.put(str,false);
}
Queue<String> queue=new LinkedList<>();
queue.offer(start);
while(!queue.isEmpty()) {
ans++;
int size=queue.size();
while(size>0) {
String tmp=queue.poll();
for(int i=0;i<len;i++) {
for(int j=0;j<26;j++) {
char ch=(char)('a'+j);
String ret=tmp.substring(0,i)+ch+tmp.substring(i+1,len);
if(ret.equals(end) && hash.containsKey(ret)) {
return ans;
}
if(hash.containsKey(ret) && hash.get(ret)==false) {
queue.offer(ret);
hash.put(ret,true);
}
}
}
size--;
}
}
return 0;
}
}
这道题跟上面的题是一样,只不过换了一个问法。这里是所以可以使用最短路径解决这道题是因为一个单词可以变换成很多的结果,同时结果单词又能变化成很多单词。
4.为高尔夫比赛砍树 https://leetcode.cn/problems/cut-off-trees-for-golf-event/description/
class Solution {
int []dx={-1,1,0,0};
int []dy={0,0,-1,1};
int ans=0;
public int cutOffTree(List<List<Integer>> forest) {
if(forest.get(0).get(0)==0) {
return -1;
}
List<int[]> list=new ArrayList<>();
int rows=forest.size();
int cols=forest.get(0).size();
for(int i =0;i<rows;i++) {
for(int j=0;j<cols;j++) {
if(forest.get(i).get(j)>1) {
list.add(new int[]{i,j});
}
}
}
//对这个数组进行排序
Collections.sort(list, (a, b) -> forest.get(a[0]).get(a[1]) - forest.get(b[0]).get(b[1]));
int row=0;
int col=0;
for(int i=0;i<list.size();i++) {
//其实在判断的时候只是要一个位置而已
//row和col是刚开始位置,肯定要按照顺序找
int temp=bfs(forest,row,col,list.get(i)[0],list.get(i)[1]);
if(temp==-1) {
return -1;
}
ans+=temp;
row=list.get(i)[0];
col=list.get(i)[1];
}
return ans;
}
public int bfs(List<List<Integer>> forest,int row,int col,int targetx,int targety) {
if(row==targetx && col==targety) {
return 0;
}
//接下里就是宽度搜索
//其实找到最小的位置,但是注意不能通行的地方
Queue<int[]> queue=new LinkedList<>();
int rows=forest.size();
int cols=forest.get(0).size();
boolean [][]nums=new boolean[rows][cols];
int ret=0;
queue.offer(new int []{row,col});
nums[row][col]=true;
while(!queue.isEmpty()) {
int size=queue.size();
ret++;
while(size>0) {
int[] temp=queue.poll();
for(int i=0;i<4;i++) {
int a=dx[i]+temp[0];
int b=dy[i]+temp[1];
if(a>=0 && a<rows && b>=0 && b<cols) {
if(forest.get(a).get(b)>0 && !nums[a][b]) {
if(a==targetx && b==targety) {
return ret;
}
queue.offer(new int[]{a,b});
nums[a][b]=true;
}
}
}
size--;
}
}
return -1;
}
}
首先我们看到题目是要求是从低到高开始砍树,先将所有大于的1位置以二元组的形式,可以使用pair,也可以使用数组的形式加入到数组中。然后对数组进行排序,先从(0,0)位置开始向最小的那个位置开始层序遍历,如果途中我们返回了-1的时候,说明四周遇到了障碍。 boolean [][]nums=new boolean[rows][cols];这个每次调用bfs函数的时候,就创建一次。如下图
当从9到10的时候,同时还会遍历8,1,3,4,6,7这些数字,如果我们将这些数字设置为不能再走了 ,此时返回的结果与预期的结果不同。所以每次进行bfs的时候都重新创建一个nums数组。