20天速通LeetCodeday15:BFS广度优先搜索

前言

上篇外面学习了深度优先搜索。今天我们来学习广度优先搜索(BFS)

BFS的核心思想:由远及近,层层扩展 ,按距离顺序遍历所有可达状态。

今天的例题,主要帮助大家来掌握:队列实现BFS的标准写法,理解入队出队,和访问标记的时机,能用BFS来解决最短路径,层级遍历,扩散类搜索等问题

200:岛屿数量

题目要求:给定一个 m×n 的二维网格 grid

'1' 表示陆地

'0' 表示水

统计岛屿数量

岛屿是由相邻的 '1' 组成(上下左右相连)

岛屿之间不相连

核心思路

遍历整个网格

  • 遇到1;新岛屿
  • 岛屿数量 count++
    使用BFS遍历当前岛屿的所有陆地格子
  • 将1入队
  • 将访问过的格子标记为0,避免重复访问
  • 堆队列中的格子,向四个方向扩展
  • 直到队列空;当前岛屿BFS完成
    重复上述两个操作遍历完所有格子,统计岛屿数量

代码实现

java 复制代码
class Solution {
    public int numIslands(char[][] grid) {
       int rows=grid.length;
       int cols=grid[0].length;
       int count=0;

        int[][] dirs= {{-1,0},{1,0},{0,-1},{0,1}}; // 上下左右

        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(grid[i][j]=='1'){
                    count++;
                    grid[i][j]='0';
                    Queue<int[]> queue=new LinkedList<>();
                    queue.offer(new int[]{i,j});

                    while(!queue.isEmpty()){
                        int[] cell=queue.poll();
                        int r=cell[0];
                        int c=cell[1];

                        for(int[] d:dirs){
                            int nr=r+d[0];
                            int nc=c+d[1];
                            if(nr>=0&&nr<rows&&nc>=0&&nc<cols&&grid[nr][nc]=='1'){
                                queue.offer(new int[]{nr,nc});
                                grid[nr][nc]='0';//标记访问
                            }
                        }
                    }
                }
            }
        }
        return count;
    }
}

总结

兄弟们可以发现这道题出现在DFS中,现在他又出现在BFS中了。

因为本质上来说DFS和BFS都是一种搜索策略。两种搜索思路都能实现题目的解答。

我们来通过这道题来分析两种搜索的实现有哪些不一样。

我们先来看相同的地方

java 复制代码
for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(grid[i][j]=='1'){
                    count++;
                    grid[i][j]='0';

这一块实现的功能都是先通过遍历找到一地块陆地。只有在找到陆地之后,我们才能开展不同的搜索策略。于是区别就来了。

我们先来看BFS:

java 复制代码
 while(!queue.isEmpty()){
                        int[] cell=queue.poll();
                        int r=cell[0];
                        int c=cell[1];

                        for(int[] d:dirs){
                            int nr=r+d[0];
                            int nc=c+d[1];
                            if(nr>=0&&nr<rows&&nc>=0&&nc<cols&&grid[nr][nc]=='1'){
                                queue.offer(new int[]{nr,nc});
                                grid[nr][nc]='0';//标记访问

BFS事先维护了一个队列queue(存放第一块陆地)。因为BFS是像病毒扩散一样从母体一步步向外扩散。其中的母体就是队列中存放的陆地坐标i,j。扩散方式就是将母体坐标进行上下左右变换。最后通过判断,是就感染。不是直接跳过

再来看DFS:

java 复制代码
 private void dfs(char[][] grid,int i,int j){
        int rows=grid.length;
        int cols=grid[0].length;
        //边界判断+遇到水就停
        if(i<0||i>=rows||j<0||j>=cols||grid[i][j]=='0'){
            return;
        }
        //把当前陆地变为水
        grid[i][j]='0';
        //向四个方向扩散
        dfs(grid, i + 1, j); // 下
        dfs(grid, i - 1, j); // 上
        dfs(grid, i, j + 1); // 右
        dfs(grid, i, j - 1); // 左
    }

DFS采用了递归/回溯的方法。扩散方式是先深度走一个分支,如果扩散完了,通过回溯的方法,回到分支的岔路口,走另一个岔路。

695:岛屿的最大面积

题目要求:给定一个 m×n 的二维网格 grid:

'1' 表示陆地

'0' 表示水

找出最大岛屿的面积

岛屿由相邻的 '1' 组成(上下左右相连)

返回面积最大的岛屿格子数量

核心思路

遍历整个网格

  • 遇到1;新岛屿
  • BFS计算该岛面积
    BFS遍历岛屿
  • 将格子(i,j)入队
  • 标记访问过
  • 队列非空时:出队格子(r,c)
  • 面积累加
  • 四个方向都检查
    更新最大面积 maxArea = Math.max(maxArea, curArea)

代码实现

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        int maxArea = 0;

        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}}; // 上下左右

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (grid[i][j] == 1) {
                    int curArea = 0;
                    Queue<int[]> queue = new LinkedList<>();
                    queue.offer(new int[]{i, j});
                    grid[i][j] = 0; // 标记访问

                    while (!queue.isEmpty()) {
                        int[] cell = queue.poll();
                        int r = cell[0], c = cell[1];
                        curArea++; // 累加面积

                        for (int[] d : dirs) {
                            int nr = r + d[0], nc = c + d[1];
                            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
                                queue.offer(new int[]{nr, nc});
                                grid[nr][nc] = 0; // 标记访问
                            }
                        }
                    }

                    maxArea = Math.max(maxArea, curArea);
                }
            }
        }

        return maxArea;
    }
}

总结

本题与上一题类似,都是有BFS和DFS两种解法。

在这里不再做两种搜索策略的区分,与上题大同小异

本题额外多的点是需要额外维护一个面积字段。根据遍历的推进进行累加。

注意点:面积累加的位置放在了while下而不是最后一个if中。

标准做法:curArea=0 +出队累加

994:腐烂的橘子

题目要求:

给定一个 m×n 的二维网格 grid:

0 → 空格子

1 → 新鲜橘子

2 → 腐烂橘子

每分钟,腐烂橘子会让 上下左右相邻的新鲜橘子腐烂

返回 使所有橘子腐烂所需的最少分钟数

如果无法全部腐烂,返回 -1

核心思路

本题天然适合BFS(层次遍历)且BFS层次可以直接表示分钟数

具体流程:
初始化队列

  • 所有腐烂橘子入队
  • 统计心思按橘子数量freshCount
    BFS逐层扩展
  • 队列非空:
  • 遍历当前层所有腐烂橘子
  • 将相邻的新鲜橘子腐烂-入队
  • freshCount--
  • 分钟数 minutes++
    结束条件
  • BFS完成
  • 如果freshCount==0;返回minutes
  • 否则;返回-1

代码实现

java 复制代码
class Solution {
    public int orangesRotting(int[][] grid) {
        int rows=grid.length;
        int cols=grid[0].length;

        Queue<int[]> queue=new LinkedList<>();
        int freshCount=0;

        //初始化
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(grid[i][j]==2){
                    queue.offer(new int[]{i,j});
                }else if(grid[i][j]==1){
                    freshCount++;
                }
            }
        }

        if(freshCount==0) return 0;//没有新鲜橘子

        int minutes=0;
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}}; // 上下左右
        while(!queue.isEmpty()){
            int size=queue.size();
            boolean rottenThisRound=false;

            for(int i=0;i<size;i++){
                int[] cell=queue.poll();
                int r=cell[0], c=cell[1];

                for(int[] d:dirs){
                    int nr=r+d[0], nc=c+d[1];
                    if(nr >= 0 && nr < rows && nc >= 0 && nc < cols &&grid[nr][nc]==1){
                        grid[nr][nc]=2;
                        queue.offer(new int[]{nr,nc});
                        freshCount--;
                        rottenThisRound=true;
                    }
                }
            }
            if(rottenThisRound) minutes++;//只有再这一轮有新鲜橘子腐烂时增加分钟
        }
        return freshCount==0?minutes:-1;
    }
}

总结

这道题是典型的BFS题目。BFS搜索就像感染一样层层递进

对于本道题我们从相同点与不同点进行理解。

相同点:

首段for循环遍历来寻找腐烂橘子并入队

最后BFS遍历方式大同小异

不同点:

在全代码中,一些需要额外维护的变量贯穿始终、

freshCount记录新鲜橘子数量,用于判断最终能否将所有橘子感染

minutes是我们最终需要返回的结果;分钟数

布尔类型的rottenThisRound;用来辅助判断minutes什么适合需要累加

相同点在前面的练习难度不大,主要是不同点。

对于不同点来说,因为题目都是字段。考察我们对于字段的运用时机(代码中的位置)是关键

比如rottenThisRound辅助minutes;我曾疑惑为什么不直接将minutes放在最后一个for循环遍历(因为最后一个循环是检查当前队列元素的四个方向相邻格子,还并没有确认感染)

最后来看,将前面两道掌握好,这题的注意点就只有如下:

注意点:

额外字段的位置

相关推荐
目黑live +wacyltd1 小时前
算法备案:常见驳回原因与应对策略
人工智能·算法
磊 子2 小时前
多态类原理+四种类型转换+异常处理
开发语言·c++·算法
染指11103 小时前
3.AI大模型-token是什么-大模型底层运行机制
人工智能·算法·机器学习
谙弆悕博士3 小时前
快速学C语言——第19章:C语言常用开发库
c语言·开发语言·算法·业界资讯·常用函数
光影少年3 小时前
前端算法题
前端·javascript·算法
南宫萧幕3 小时前
基于 Simulink 与 Python 联合仿真的 eVTOL 强化学习全链路实战
开发语言·人工智能·python·算法·机器学习·控制
电魂泡哥3 小时前
CMS垃圾回收
java·jvm·算法
hkj88084 小时前
CRC-512算法输出64字节
算法