【BFS】《单源、多源 BFS:图搜索算法的双生力量》

news2025/3/31 3:55:30

文章目录

  • 前言
  • 单源BFS例题
    • 一、迷宫中离入口最近的出口
    • 二、 最小基因变化
    • 三、单词接龙
    • 四、为高尔夫比赛砍树
  • 多源BFS例题
    • 一、 01 矩阵
    • 二、飞地的数量
    • 三、地图中的最高点
    • 四、地图分析
  • 结语


在这里插入图片描述

前言

什么是单源、多源BFS算法问题呢?

BFS(Breadth - First Search)即广度优先搜索算法,单源和多源 BFS 算法问题主要用于解决图或网格中的最短路径问题 ,二者具体介绍如下:
单源 BFS 算法问题:指从图中的一个起始节点(源点)出发,按照广度优先的策略,逐层向外扩展访问其他节点,找到从该源点到图中其他各个可达节点的最短路径。例如在一个迷宫网格中,从一个特定入口出发找出口,就可以使用单源 BFS。该算法适用于无权图或所有边权值都相同的图。它利用队列来存储待访问节点,先将源点入队,然后不断取出队首节点并访问其相邻未访问节点,将这些相邻节点入队,直到队列为空或找到目标节点。
多源 BFS 算法问题:是有多个起始节点(源点),目标是找到从这些多个源点到图中其他各个可达节点的最短路径。例如在一个城市地图中,有多个垃圾回收站(源点),要计算各个小区到最近垃圾回收站的距离,就可能用到多源 BFS。多源 BFS 的核心在于初始化队列时将所有源点都放入队列,之后和单源 BFS 类似,按广度优先顺序访问节点并扩展,直到遍历完所有可达节点。它也主要用于边权为 1 或边权相等的情况 。
解决多源 BFS 问题时,若直接转化为若干个单源最短路问题求解,可能因重复查找过多而超时。更好的方法是把所有源点视为一个 “超级源点” ,将问题转化为单源最短路问题来处理。

下面,本片文章将通过例题为大家详细介绍单源、多源BFS算法!

单源BFS例题

一、迷宫中离入口最近的出口

  1. 题目链接:迷宫中离入口最近的出口
  2. 题目描述:

给你⼀个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格⼦(⽤ ‘.’ 表示)和墙(⽤ ‘+’ 表示)。同时给你迷宫的⼊⼝ entrance ,用 entrance = [entrancerow, entrancecol] 表示你⼀开始所在格子的行和列。 每⼀步操作,你可以往上,下,左或者右移动⼀个格子。你不能进⼊墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance 最近 的出口。出口的含义是 maze 边界上的空格子。
entrance 格子不算出口。 请你返回从 entrance 到最近出⼝的最短路径的步数 ,如果不存在这样的路径,请你返回 -1 。
⽰例 1:
在这里插入图片描述
输⼊:maze = [[“+”,“+”,“.”,“+”],[“.”,“.”,“.”,“+”],[“+”,“+”,“+”,“.”]], entrance = [1,2]
输出:1
解释:总共有 3 个出⼝,分别位于 (1,0),(0,2) 和 (2,3) 。 ⼀开始,你在⼊⼝格⼦ (1,2) 处。
◦ 你可以往左移动 2 步到达 (1,0) 。
◦ 你可以往上移动 1 步到达 (0,2) 。 从入口处没法到达 (2,3) 。
所以,最近的出口是 (0,2) ,距离为 1 步。
⽰例 2:
在这里插入图片描述
输⼊:maze = [[“+”,“+”,“+”],[“.”,“.”,“.”],[“+”,“+”,“+”]], entrance = [1,0]
输出:2
解释:迷宫中只有 1 个出口,在 (1,2) 处。(1,0) 不算出口,因为它是入口格⼦。 初始时,你在入口与格⼦ (1,0) 处。
◦ 你可以往右移动 2 步到达 (1,2) 处。 所以,最近的出口为 (1,2) ,距离为 2 步。
⽰例 3:
在这里插入图片描述
输⼊:maze = [[“.”,“+”]], entrance = [0,0]
输出:-1
解释:这个迷宫中没有出⼝。
提示:
maze.length == m
maze[i].length == n
1 <= m, n <= 100
maze[i][j] 要么是 ‘.’ ,要么是 ‘+’ 。
entrance.length == 2
0 <= entrancerow < m
0 <= entrancecol < n
entrance ⼀定是空格子。

  1. 解法(bfs 求最短路):
    算法思路: 利⽤层序遍历来解决迷宫问题,是最经典的做法。 我们可以从起点开始层序遍历,并且在遍历的过程中记录当前遍历的层数。这样就能在找到出口的时候,得到起点到出口的最短距离。

  2. 代码示例:

  int[] dx = {0, 0, -1, 1};
  int[] dy = {1, -1, 0, 0};
 public int nearestExit(char[][] maze, int[] e) {
        int m = maze.length, n = maze[0].length;
        boolean[][] vis = new boolean[200][200];
        Queue<int[]> q = new LinkedList<>();
        q.add(new int[]{e[0], e[1]});
        vis[e[0]][e[1]] = true;
        int step = 0;
        while (!q.isEmpty()) {
            step++;
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                int[] t = q.poll();
                int a = t[0], b = t[1];
                for (int j = 0; j < 4; j++) {
                    int x = a + dx[j], y = b + dy[j];
                    if (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == '.' && !vis[x][y]) {
                        if (x == 0 || x == m - 1 || y == 0 || y == n - 1) return step;
                        vis[x][y] = true;
                        q.add(new int[]{x, y});
                    }
                }
            }
        }
        return -1;
    }

二、 最小基因变化

  1. 题目链接:最小基因变化
  2. 题目描述:

基因序列可以表示为⼀条由 8 个字符组成的字符串,其中每个字符都是 ‘A’ 、 ‘C’ 、 ‘G’ 和’T’ 之⼀。 假设我们需要调查从基因序列 start 变为 end 所发⽣的基因变化。⼀次基因变化就意味着这个 基因序列中的⼀个字符发⽣了变化。
• 例如, “AACCGGTT” --> “AACCGGTA” 就是⼀次基因变化。 另有⼀个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。 (变化后的基因必须位于基因库 bank 中) 给你两个基因序列 start 和 end ,以及⼀个基因库 bank ,请你找出并返回能够使 start变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。
注意:起始基因序列 start 默认是有效的,但是它并不⼀定会出现在基因库中
示例 1:
输入:start = “AACCGGTT”, end = “AACCGGTA”, bank = [“AACCGGTA”]
输出:1
⽰例 2:
输⼊:start = “AACCGGTT”, end = “AAACGGTA”, bank =
[“AACCGGTA”,“AACCGCTA”,“AAACGGTA”]
输出:2
⽰例 3:
输入:start = “AAAAACCC”, end = “AACCCCCC”, bank =[“AAAACCCC”,“AAACCCCC”,“AACCCCCC”]
输出:3
提示:
◦ start.length == 8
◦ end.length == 8
◦ 0 <= bank.length <= 10
◦ bank[i].length == 8
◦ start 、 end 和 bank[i] 仅由字符 [‘A’, ‘C’, ‘G’, ‘T’] 组成

  1. 解法:
    算法思路: 如果将「每次字符串的变换」抽象成图中的「两个顶点和⼀条边」的话,问题就变成了「边权为 1 的最短路问题」。
    因此,从起始的字符串开始,来⼀次 bfs 即可。

  2. 代码示例:

 int[] dx = {0, 0, -1, 1};
 int[] dy = {1, -1, 0, 0};
 public int minMutation(String startGene, String endGene, String[] bank) {
        Set<String> vis = new HashSet<>();//用来标记已经搜索过的状态
        Set<String> hash = new HashSet<>();//用来统计基因库里的字符串
        for (String s : bank) {
            hash.add(s);
        }
        char[] charge = {'A', 'C', 'G', 'T'};
        if (startGene.equals(endGene)) {
            return 0;
        }
        if (!hash.contains(endGene)) return -1;
        Queue<String> q = new LinkedList<>();
        q.add(startGene);
        vis.add(startGene);
        int step = 0;
        while (!q.isEmpty()) {
            step++;
            int sz = q.size();
            while (sz-- != 0) {
                String t = q.poll();
                for (int i = 0; i < 8; i++) {
                    char[] tmp = t.toCharArray();
                    for (int j = 0; j < 4; j++) {
                        tmp[i] = charge[j];
                        String next = new String(tmp);
                        if (hash.contains(next) && !vis.contains(next)) {
                            if (next.equals(endGene)) return step;
                            q.add(next);
                            vis.add(next);
                        }
                    }
                }
            }
        }
        return -1;
    }

三、单词接龙

  1. 题目链接:单词接龙
  2. 题目描述:

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是⼀个按下述规格形成的序 列 beginWord -> s(1) -> s(2) -> … -> s(k) :
• 每⼀对相邻的单词只差⼀个字⺟。 • 对于 1 <= i <= k 时,每个 s(i) 都在 wordList 中。注意, beginWord 不需要 在 wordList 中。
• s(k) == endWord 给你两个单词 beginWord 和 endWord 和⼀个字典 wordList ,返回 从 beginWord 到endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
⽰例 1:
输⼊:beginWord = “hit”, endWord = “cog”, wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出:5
解释:⼀个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”, 返回它的长度 5。
⽰例 2:
输⼊:beginWord = “hit”, endWord = “cog”, wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出:0
解释:endWord “cog” 不在字典中,所以无法进行转换。
提示:
◦ 1 <= beginWord.length <= 10
◦ endWord.length == beginWord.length
◦ 1 <= wordList.length <= 5000
◦ wordList[i].length == beginWord.length
◦ beginWord 、 endWord 和 wordList[i] 由⼩写英⽂字⺟组成 ◦ beginWord != endWord
◦ wordList 中的所有字符串互不相同

  1. 解法:
    算法思路:本题的算法思路与上题相似,大家可以自行尝试编写代码提高代码能力。
  2. 代码示例:
int[] dx = {0, 0, -1, 1};
int[] dy = {1, -1, 0, 0};
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> hash = new HashSet<>();
        for (String s : wordList) hash.add(s);
        Set<String> vis = new HashSet<>();
        if (!hash.contains(endWord)) return 0;
        Queue<String> q = new LinkedList<>();
        vis.add(beginWord);
        q.add(beginWord);
        int ret = 1;
        while (!q.isEmpty()) {
            ret++;
            int size = q.size();
            while (size-- != 0) {
                String t = q.poll();
                for (int i = 0; i < t.length(); i++) {
                    char[] tmp = t.toCharArray();
                    for (char ch = 'a'; ch <= 'z'; ch++) {
                        tmp[i] = ch;
                        String next = new String(tmp);
                        if (hash.contains(next) && !vis.contains(next)) {
                            if (next.equals(endWord)) return ret;
                            q.add(next);
                            vis.add(next);
                        }
                    }
                }
            }
        }
        return 0;
    }

四、为高尔夫比赛砍树

  1. 题目链接:为高尔夫比赛砍树
  2. 题目描述:

你被请来给⼀个要举办高尔夫比赛的树林砍树。树林由⼀个 m x n 的矩阵表示, 在这个矩阵中:
◦ 0 表⽰障碍,无法触碰
◦ 1 表示地⾯,可以行走
◦ ⽐ 1 ⼤的数 表⽰有树的单元格,可以行走,数值表⽰树的⾼度
每⼀步,你都可以向上、下、左、右四个⽅向之⼀移动⼀个单位,如果你站的地方有⼀棵树,那么你 可以决定是否要砍倒它。 你需要按照树的高度从低向⾼砍掉所有的树,每砍过⼀颗树,该单元格的值变为 1 (即变为地面)。你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你⽆法砍完所有的树,返回 -1 。 可以保证的是,没有两棵树的⾼度是相同的,并且你至少需要砍倒⼀棵树。
⽰例 1:
在这里插入图片描述
输⼊:forest = [[1,2,3],[0,0,4],[7,6,5]]
输出:6
解释:沿着上⾯的路径,你可以⽤ 6 步,按从最矮到最⾼的顺序砍掉这些树。
⽰例 2:
在这里插入图片描述
输⼊:forest = [[1,2,3],[0,0,0],[7,6,5]]
输出:-1
解释:由于中间⼀⾏被障碍阻塞,⽆法访问最下⾯⼀⾏中的树。
示例 3:
输入:forest = [[2,3,4],[0,0,5],[8,7,6]]
输出:6
解释:可以按与⽰例 1 相同的路径来砍掉所有的树
提⽰:
◦ m == forest.length
◦ n == forest[i].length
◦ 1 <= m, n <= 50
◦ 0 <= forest[i][j] <= 10^9

  1. 解法:
    a. 先找出砍树的顺序;
    b. 然后按照砍树的顺序,⼀个⼀个的⽤ bfs 求出最短路即可
  2. 代码示例:
 	int m, n;
    int[] dx = {0, 0, 1, -1};
    int[] dy = {1, -1, 0, 0};
    public int cutOffTree(List<List<Integer>> f) {
        m = f.size();
        n = f.get(0).size();
        //准备工作:确定看书顺序
        List<int[]> trees = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (f.get(i).get(j) > 1) {
                    trees.add(new int[]{i, j});
                }
            }
        }
        Collections.sort(trees, (a, b) -> {
            return f.get(a[0]).get(a[1]) - f.get(b[0]).get(b[1]);
        });
        //按照顺序砍树
        int ret = 0;
        int bx = 0, by = 0;
        for (int[] tree : trees) {
            int x = tree[0], y = tree[1];
            int step = bfs(f, bx, by, x, y);
            if (step == -1) return -1;
            ret += step;
            bx = x;
            by = y;
        }
        return ret;
    }
    public int bfs(List<List<Integer>> f, int bx, int by, int ex, int ey) {
        if (bx == ex && by == ey) {
            return 0;
        }
        Queue<int[]> q = new LinkedList<>();
        boolean[][] vis = new boolean[m][n];
        q.add(new int[]{bx, by});
        vis[bx][by] = true;
        int step = 0;
        while (!q.isEmpty()) {
            step++;
            int sz = q.size();
            while (sz-- != 0) {
                int[] t = q.poll();
                int a = t[0], b = t[1];
                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 && f.get(x).get(y) != 0 && !vis[x][y]) {
                        if (x == ex && y == ey) {
                            return step;
                        }
                        vis[x][y] = true;
                        q.add(new int[]{x, y});
                    }
                }
            }
        }
        return -1;
    }

多源BFS例题

一、 01 矩阵

  1. 题目链接:01矩阵
  2. 题目描述:

给定⼀个由 0 和 1 组成的矩阵 mat ,请输出⼀个⼤⼩相同的矩阵,其中每⼀个格子是mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1:
在这里插入图片描述
输⼊:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]
示例 2:
在这里插入图片描述
输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]
提⽰:
m == mat.length
n == mat[i].length
1 <= m, n <= 104
1 <= m * n <= 104
mat[i][j] is either 0 or 1.
mat 中⾄少有⼀个 0

  1. 解法(bfs)(多个源头的最短路问题)
    算法思路: 对于求的最终结果,我们有两种方式:
    • 第⼀种⽅式:从每⼀个 1 开始,然后通过层序遍历找到离它最近的 0 。 这⼀种⽅式,我们会以所有的 1 起点,来⼀次层序遍历,势必会遍历到很多重复的点。并且如果 矩阵中只有⼀个 0 的话,每⼀次层序遍历都要遍历很多层,时间复杂度较⾼。
    • 换⼀种⽅式:从 0 开始层序遍历,并且记录遍历的层数。当第⼀次碰到 1 的时候,当前的层数 就是这个 1 离 0 的最短距离。 这⼀种方式,我们在遍历的时候标记⼀下处理过的 1 ,能够做到只⽤遍历整个矩阵⼀次,就能得 到最终结果。
    但是,这里有⼀个问题, 0 是有很多个的,我们怎么才能保证遇到的 1 距离这⼀个 0 是最近的呢?
    其实很简单,我们可以先把所有的 0 都放在队列中,把它们当成⼀个整体,每次把当前队列⾥⾯的所 有元素向外扩展⼀次。
  2. 代码示例:
 int[] dx = {0, 0, 1, -1};
 int[] dy = {1, -1, 0, 0};
  public int[][] updateMatrix(int[][] mat) {
        int m = mat.length;
        int n = mat[0].length;
        int[][] dist = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = -1;
            }
        }
        Queue<int[]> q = new LinkedList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (mat[i][j] == 0) {
                    dist[i][j] = 0;
                    q.add(new int[]{i, j});
                }
            }
        }
        while (!q.isEmpty()) {
            int[] t = q.poll();
            int a = t[0];
            int b = t[1];
            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.add(new int[]{x, y});
                }
            }
        }
        return dist;
    }
 

二、飞地的数量

  1. 题目链接:飞地的数量
  2. 题目描述:

给你⼀个大小为 m x n 的⼆进制矩阵 grid ,其中 0 表示⼀个海洋单元格、 1 表示⼀个陆地 单元格。 ⼀次移动是指从⼀个陆地单元格走到另⼀个相邻(上、下、左、右)的陆地单元格或跨过 grid的边界。 返回网格中无法在任意次数的移动中离开⽹格边界的陆地单元格的数量。
示例一:
在这里插入图片描述
输⼊:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。⼀个 1 没有被包围,因为它在边界上。
示例 2:
在这里插入图片描述
输⼊:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。
提⽰:
◦ m == grid.length
◦ n == grid[i].length
◦ 1 <= m, n <= 500
◦ grid[i][j] 的值为 0 或 1

  1. 解法:
    算法思路: 正难则反: 从边上的 1 开始搜索,把与边上 1 相连的联通区域全部标记⼀下; 然后再遍历⼀遍矩阵,看看哪些位置的 1 没有被标记即可 标记的时候,可以用「多源 bfs 」解决。
  2. 代码示例:
 int[] dx = {0, 0, 1, -1};
 int[] dy = {1, -1, 0, 0};
   public int numEnclaves(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        Queue<int[]> q = new LinkedList<>();
        boolean[][] vis = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {
                    if (grid[i][j] == 1) {
                        q.add(new int[]{i, j});
                        vis[i][j] = true;
                    }
                }
            }
        }
        while (!q.isEmpty()) {
            int[] t = q.poll();
            int a = t[0], b = t[1];
            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]) {
                    vis[x][y] = true;
                    q.add(new int[]{x, y});
                }
            }
        }
        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;
    }
 

三、地图中的最高点

  1. 题目链接:地图中的最高点
  2. 题目描述:

给你⼀个大小为 m x n 的整数矩阵 isWater ,它代表了⼀个由陆地 和水域 单元格组成的地图。
• 如果 isWater[i][j] == 0 ,格子(i, j) 是⼀个陆地格⼦。
• 如果 isWater[i][j] == 1 ,格子(i, j) 是⼀个水域格⼦。 你需要按照如下规则给每个单元格安排高度:
• 每个格⼦的高度都必须是非负的。
• 如果⼀个格子是水域 ,那么它的高度必须为 0 。
• 任意相邻的格高度差至多 为 1 。当两个格子在正东、南、西、北方向上相互紧挨着,就称它们为相邻的格⼦。(也就是说它们有⼀条公共边) 找到⼀种安排高度的方案,使得矩阵中的最高高度值最大 。 请你返回⼀个大小为 m x n 的整数矩阵 height ,其中 height[i][j] 是格⼦ (i, j) 的高度。如果有多种解法,请返回任意⼀个 。
示例 1:
在这里插入图片描述
输入:isWater = [[0,1],[0,0]]
输出:[[1,0],[2,1]]
解释:上图展示了给各个格⼦安排的高度。 蓝⾊格子是水域格,绿色格子是陆地格。
示例二:
在这里插入图片描述
输⼊:isWater = [[0,0,1],[1,0,0],[0,0,0]]
输出:[[1,1,0],[0,1,1],[1,2,2]]
解释:所有安排⽅案中,最⾼可行高度为 2 。 任意安排方案中,只要最高高度为 2 且符合上述规则的,都为可行方案。
提示:
◦ m == isWater.length
◦ n == isWater[i].length
◦ 1 <= m, n <= 1000
◦ isWater[i][j] 要么是 0 ,要么是 1 。 ◦
⾄少有 1 个水域格子。

  1. 解法: 算法思路:
    01矩阵的变型题,直接用多源 bfs 解决即可。
  2. 代码示例:
 int[] dx = {0, 0, 1, -1};
 int[] dy = {1, -1, 0, 0};    
 public int[][] highestPeak(int[][] isWater) {
        int m = isWater.length, n = isWater[0].length;
        int[][] dist = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = -1;
            }
        }
        Queue<int[]> q = new LinkedList<>();
        //所有的源点加入到序列里
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (isWater[i][j] == 1) {
                    q.add(new int[]{i, j});
                    dist[i][j] = 0;
                }
            }
        }
        while (!q.isEmpty()) {
            int[] t = q.poll();
            int a = t[0], b = t[1];
            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.add(new int[]{x, y});
                }
            }
        }
        return dist;
    }

四、地图分析

  1. 题目链接:地图分析
  2. 题目描述:

你现在⼿⾥有⼀份⼤⼩为 n x n 的网格 grid ,上⾯的每个 单元格 都⽤ 0 和 1 标记好了。 其中 0 代表海洋, 1 代表陆地。 请你找出⼀个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1 。
我们这里说的距离是「曼哈顿距离」( Manhattan Distance): (x0, y0) 和 (x1, y1) 这两 个单元格之间的距离是 |x0 - x1| + |y0 - y1|
示例 1:
在这里插入图片描述
输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2
⽰例 2:
在这里插入图片描述
输⼊:grid = [[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释:
海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最⼤,最大距离为 4。
提示:
◦ n == grid.length
◦ n == grid[i].length
◦ 1 <= n <= 100
◦ grid[i][j] 不是 0 就是 1

  1. 解法:
    算法思路:
    01矩阵的变型题,直接用多源 bfs 解决即可。

  2. 代码示例:

 int[] dx = {0, 0, 1, -1};
 int[] dy = {1, -1, 0, 0};    
  public int maxDistance(int[][] grid) {
        int ret = -1;
        int m = grid.length, n = grid[0].length;
        int[][] dist = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = -1;
            }
        }
        Queue<int[]> q = new LinkedList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    dist[i][j] = 0;
                    q.add(new int[]{i, j});
                }
            }
        }
        while (!q.isEmpty()) {
            int[] t = q.poll();
            int a = t[0], b = t[1];
            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;
                    ret = Math.max(ret, dist[x][y]);
                    q.add(new int[]{x, y});
                }
            }
        }
        return ret;
    }

结语

本文到这里就结束了,主要介绍了解决单源多源BFS问题,并通过例题深化了代码步骤,本篇文章题目十分考验代码能力,大家一定要自己尝试哦!

以上就是本文全部内容,感谢各位能够看到最后,创作不易,希望大家多多支持!

最后,大家再见!祝好!我们下期见!

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

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

相关文章

【2025】基于springboot+vue的医院在线问诊系统设计与实现(源码、万字文档、图文修改、调试答疑)

基于Spring Boot Vue的医院在线问诊系统设计与实现功能结构图如下&#xff1a; 课题背景 随着互联网技术的飞速发展和人们生活水平的不断提高&#xff0c;传统医疗模式面临着诸多挑战&#xff0c;如患者就医排队时间长、医疗资源分配不均、医生工作压力大等。同时&#xff0c;…

STM32基础教程——PWM驱动舵机

目录 前言 技术实现 原理图 接线图 代码实现 内容要点 PWM基本结构 开启外设时钟 配置GPIO端口 配置时基单元 初始化输出比较单元 调整PWM占空比 输出比较通道重映射 舵机角度设置 实验结果 问题记录 前言 舵机&#xff08;Servo&#xff09;是一种位置&#xff…

odata 搜索帮助

参考如下链接&#xff1a; FIORI ELement list report 细节开发&#xff0c;设置过滤器&#xff0c;搜索帮助object page跳转等_fiori element label 变量-CSDN博客 注&#xff1a;odata搜索帮助可以直接将值带出来&#xff0c;而不需要进行任何的重定义 搜索帮助metedata配置…

Docker基本命令VS Code远程连接

Docker基本命令 创建自己的docker容器&#xff1a;docker run --net host --name Container_name --gpus all --shm-size 1t -it -v Your_Path:Your_Dir mllm:mac /bin/bashdocker run&#xff1a;用于创建并启动一个新容器-name&#xff1a;为当前新建的容器命名-gpus&#x…

大疆上云api直播功能如何实现

概述 流媒体服务器作为直播画面的中转站,它接收推流端的相机画面,同时拉流端找它获取相机的画面。整个流程如下: 在流媒体服务器上创建流媒体应用(app),一个流媒体服务器上面可以创建多个流媒体应用约定推拉流的地址。假设流媒体服务器工作在1935端口上面,假设创建的流…

理解文字识别:一文读懂OCR商业化产品的算法逻辑

文字识别是一项“历久弥新”的技术。早在上世纪初&#xff0c;工程师们就开始尝试使用当时有限的硬件设备扫描并识别微缩胶片、纸张上的字符。随着时代和技术的发展&#xff0c;人们在日常生活中使用的电子设备不断更新换代&#xff0c;文字识别的需求成为一项必备的技术基础&a…

使用 Cursor、MCP 和 Figma 实现工程化项目自动化,提升高达 200% 效率

直接上手不多说其他的&#xff01; 一、准备动作 1、Cursor下载安卓 1.1访问官方网站 打开您的网络浏览器&#xff0c;访问 Cursor 的官方网站&#xff1a;https://www.cursor.com/cn 1.2开始下载: 点击"Download for free" 根据您的浏览器设置&#xff0c;会自…

Arduino、ESP32驱动GUVA-S12SD UV紫外线传感器(光照传感器篇)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 UV紫外线传感器是一个测试紫外线总量的最佳传感器,它不需要使用波长滤波器,只对紫外线敏感。 Arduino UV紫外线传感器,直接输出对应紫外线指数(UV INDEX)的线性电压,输出电压范围大约0~1100mV(对应UV INDEX值…

PTA 1097-矩阵行平移

给定一个&#x1d45b;&#x1d45b;nn的整数矩阵。对任一给定的正整数&#x1d458;<&#x1d45b;k<n&#xff0c;我们将矩阵的奇数行的元素整体向右依次平移1、……、&#x1d458;、1、……、&#x1d458;、……1、……、k、1、……、k、……个位置&#xff0c;平移…

Notepad++ 替换 换行符 为 逗号

多行转一行&#xff0c;逗号分隔 SPO2025032575773 SPO2025032575772 SPO2025032575771 SPO2025032575771 SPO2025032575770为了方便快速替换&#xff0c;我们需要先知道这样类型的数据都存在哪些换行符。 点击【视图】-【显示符号】-【显示行尾符】 对于显示的行尾换行符【C…

使用飞书API自动化更新共享表格数据

飞书API开发之自动更新共享表格 天马行空需求需求拆解1、网站数据爬取2、飞书API调用2.1 开发流程2.2 创建应用2.3 配置应用2.4 发布应用2.5 修改表格权限2.6 获取tenant_access_token2.7 调用API插入数据 总结 天马行空 之前一直都是更新的爬虫逆向内容&#xff0c;工作中基本…

使用vscode搭建pywebview集成vue项目示例

文章目录 前言环境准备项目源码下载一、项目说明1 目录结构2 前端项目3 后端项目获取python安装包(选择对应版本及系统) 三、调试与生成可执行文件1 本地调试2 打包应用 四、核心代码说明1、package.json2、vite.config.ts设置3、main.py后端入口文件说明 参考文档 前言 本节我…

蓝桥杯嵌入式十六届模拟三

由硬件框图可以知道我们要配置LED 和按键 一.LED 先配置LED的八个引脚为GPIO_OutPut,锁存器PD2也是,然后都设置为起始高电平,生成代码时还要去解决引脚冲突问题 二.按键 按键配置,由原理图按键所对引脚要GPIO_Input 生成代码,在文件夹中添加code文件夹,code中添加fun.…

onedav一为导航批量自动化导入网址(完整教程)

OneNav作为一个功能强大的导航工具,支持后台管理、加密链接、浏览器书签批量导入等功能,能够帮助用户轻松打造专属的导航页面。今天,我将为大家详细介绍如何实现OneNav导航站的批量自动化导入网址。 1、建立要批量导入的表格 格局需要创建表格,表格的要求是一定要有需要,…

Linux之编辑器vim命令

vi/vim命令&#xff1a; 终端下编辑文件的首选工具&#xff0c;号称编辑器之神 基本上分为三种模式&#xff0c;分别是 命令模式&#xff08;command mode&#xff09;>输入vi的命令和快捷键&#xff0c;默认打开文件的时候的模式插入模式&#xff08;insert mode&#x…

备赛蓝桥杯之第十六届模拟赛2期职业院校组第四题:地址识别

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

多模态自动驾驶混合渲染HRMAD:将NeRF和3DGS进行感知验证和端到端AD测试

基于3DGS和NeRF的三维重建技术在过去的一年中取得了快速的进步&#xff0c;动态模型也变得越来越普遍&#xff0c;然而这些模型仅限于处理原始轨迹域内的对象。 HRMAD作为一种混合方案&#xff0c;将传统的基于网格的动态三维神经重建和物理渲染优势结合&#xff0c;支持在任意…

mac m3 pro 部署 stable diffusion webui

什么是Stable Diffusion WebUI &#xff1f; Stable Diffusion WebUI 是一个基于Stable Diffusion模型开发的图形用户界面&#xff08;GUI&#xff09;工具。通过这个工具&#xff0c;我们可以很方便的基于提示词&#xff0c;描述一段文本来指导模型生成相应的图像。相比较通过…

多层感知机实现

激活函数 非线性 ReLU函数 修正线性单元 rectified linear unit relu(x)max(0,x) relu的导数&#xff1a; sigmoid函数 s i g m o i d ( x ) 1 1 e − x sigmoid(x)\frac{1}{1e^{-x}} sigmoid(x)1e−x1​ 是一个早期的激活函数 缺点是&#xff1a; 幂运算相对耗时&…

Linux笔记---动静态库(使用篇)

目录 1. 库的概念 2. 静态库&#xff08;Static Libraries&#xff09; 2.1 静态库的制作 2.2 静态库的使用 2.2.1 显式指定库文件及头文件路径 2.2.2 将库文件安装到系统目录 2.2.3 将头文件安装到系统目录 3. 动态库 3.1 动态库的制作 3.2 动态库的使用 3.2.1 显式…