多源BFS最短路---矩阵 | 飞地的数量 | 地图中的最高点 | 地图分析

一.542. 01 矩阵

题目描述

给定一个由 01 组成的矩阵 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到最近的零的距离矩阵

将所有0看成水龙头,是水源,逐层往外蔓延。

最先被蔓延到的1就是最短距离,蔓延到的1矩阵位置距离+1,并且入队

再次进行bfs蔓延,直到所有的1都被水蔓延到,即可求出所有1到最近0的距离矩阵

代码实现

cpp 复制代码
/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
 //队列节点:用来存储坐标
 typedef struct {
    int x, y;
 }QNode;

 //四个方向
 int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};

int** updateMatrix(int** mat, int matSize, int* matColSize, int* returnSize, int** returnColumnSizes) {
    //对矩阵里每一个位置,算出它到最近的 0 的最短距离,最后输出距离矩阵。
    //多源bfs,多个起点入队
    //1.网格上下左右移动,边权为1,bfs求最短路
    //2.所有的0都是源头,一次性全部入队
    //3.逐层向外扩散,每走一步距离+1;第一次访问到1时就是最近零的最短距离
    //4.用距离矩阵res存储答案,初始标记为-1,0的位置直接置为0并入队
    
    //1.初始化值
    int m = matSize;
    int n = matColSize[0];
    *returnSize = m;

    //分配每行每列数组
    *returnColumnSizes = (int*)malloc(sizeof(int) * m);
    for(int i = 0; i < m; i++){
        (*returnColumnSizes)[i] = n;
    }

    //2.队列初始化
    QNode* queue = (QNode*)malloc(sizeof(QNode) * m * n);
    int front = 0, rear = 0;

    //遍历矩阵,所有0入队,距离置零;1标记未访问-1
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            if(mat[i][j] == 0){
                res[i][j] = 0;
                queue[rear++] = (QNode){i, j};
            }else {
                res[i][j] = -1;
            }
        }
    }

    //3.多源BFS扩散
    while(front < rear) {
        QNode cur = queue[front++];
        int x = cur.x;
        int y = cur.y;

        //四个方向遍历
        for(int d = 0; d < 4; d++) {
            int nx = x + dir[d][0];
            int ny = y + dir[d][1];
            //坐标合法,或者未访问
            if(nx >= 0 && nx < m && ny >= 0 && ny < n && res[nx][ny] == -1){
                res[nx][ny] = res[x][y] + 1;
                queue[rear++] = (QNode){nx, ny};
            }
        }
    }

    free(queue);
    return res;


}

总结

多个起点同时入队,适合求到最近目标点距离

二.1020. 飞地的数量

题目描述

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右 )的陆地单元格或跨过 grid 的边界。

返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。

示例 1:

复制代码
输入: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] 的值为 01

解题思路

先将边界上的岛屿找出来,即先遍历四个边界将1找出,利用bfs多源起点搜索,逐层将连起来的1入队,并标记为0,找出所有"边缘陆地",将其标记为0, 剩下的就是我们要找的飞地,直接遍历便可找到。

代码实现

cpp 复制代码
// 队列结构体:存储坐标 i,j
typedef struct {
    int x, y;
} Point;

int numEnclaves(int** grid, int gridSize, int* gridColSize) {
    int m = gridSize;
    int n = gridColSize[0];
    // 四个方向:上下左右
    int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};

    // 队列,最多 m*n 个点
    Point* queue = (Point*)malloc(sizeof(Point) * m * n);
    int rear = 0, front = 0;

    // 1. 多源入队:四条边界所有陆地
    // 左右两列
    for(int i = 0; i < m; i++){
        if(grid[i][0] == 1){
            queue[rear++] = (Point){i, 0};
            grid[i][0] = 0;//标记为已访问
        }
        if(grid[i][n - 1] == 1){
            queue[rear++] = (Point){i, n - 1};
            grid[i][n - 1] = 0;
        }
    }
    // 上下两行(跳过左右两端,已经处理)
    for(int j = 1; j < n - 1; j++){
        if(grid[0][j] == 1){
            queue[rear++] = (Point){0, j};
            grid[0][j] = 0;
        }
        if(grid[m - 1][j] == 1){
            queue[rear++] = (Point){m - 1, j};
            grid[m - 1][j] = 0;
        }
    }

    // 2. 多源 BFS
    while (front < rear) {
        Point cur = queue[front++];
        for(int i = 0; i < 4; i++){
            //四个方向搜索
            int nx = cur.x + dir[i][0];
            int ny = cur.y + dir[i][1];
            //如果合规,且是1,则加入队列,标记为0

            if(nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1){
                queue[rear++] = (Point){nx, ny};
                grid[nx][ny] = 0;
            }
        }
    }

    // 3. 统计剩余1的数量(飞地)
    int ans = 0;
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            if(grid[i][j] == 1) ans++;
        }
    }

    free(queue);
    return ans;
}

总结

先找到边界的1,多源bfs逐层标记,最后再找飞地。

三.1765. 地图中的最高点

题目描述

给你一个大小为 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]]
解释:上图展示了给各个格子安排的高度。
蓝色格子是水域格,绿色格子是陆地格。

示例 2:

复制代码
输入: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 个水域格子。

解题思路

将"让最高高度尽可能大"转化成"它到最近水域的最短距离",便可以从水源开始多源bfs向外层层扩散,扩散之后高度+1,最后输出最终高度矩阵。

代码实现

cpp 复制代码
/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
typedef struct {
    int x, y;
}Point;

int** highestPeak(int** isWater, int isWaterSize, int* isWaterColSize, int* returnSize, int** returnColumnSizes) {
    //将"让最高高度尽可能大"转化成"它到最近水域的最短距离"


    //1.初始化
    int m = isWaterSize;
    int n = isWaterColSize[0];

    *returnSize = m;
    *returnColumnSizes = (int*)malloc(sizeof(int) * m);

    //结果矩阵初始化
    int** height = (int**)malloc(sizeof(int*) * m);
    for(int i = 0; i < m; i++){
        height[i] = (int*)malloc(sizeof(int) * n);
        memset(height[i], -1, n * sizeof(int));
        (*returnColumnSizes)[i] = n;//给每一行列长度赋值
    }

    int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    //2.创建队列
    Point* q = (Point*)malloc(sizeof(Point) * m *n);//最多m * n个点
    int rear = 0, front = 0;

    //多源入队
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            if(isWater[i][j] == 1){
                height[i][j] = 0;
                q[rear++] = (Point){i, j};
            }
        }
    }

    //BFS扩散
    while(front < rear){
        Point cur = q[front++];
        int h = height[cur.x][cur.y];
        for(int d = 0; d < 4; d++){
            int nx = cur.x + dir[d][0];
            int ny = cur.y + dir[d][1];

            //未访问的陆地
            if(nx >= 0 && nx < m && ny >= 0 && ny < n && height[nx][ny] == -1){
                height[nx][ny] = h + 1;
                q[rear++] = (Point){nx, ny};
            }
        }
    }
    free(q);
    return height;

}

总结

多源bfs,将寻找最高地问题转化为,寻找距离水源最远地的问题。

四.1162. 地图分析

题目描述

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 01 标记好了。其中 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

解题思路

和上一题几乎一样,这道题是将所有陆地作为多源bfs起点,逐层向外扩散,每扩散一层距离dis+1,最后输出maxdis即可。

代码实现

cpp 复制代码
typedef struct {
    int x, y;
} Point;

int maxDistance(int** grid, int gridSize, int* gridColSize) {
    //核心思路:所有陆地都是多源起点,距离初始为0
    //bfs逐层扩散,每走一层距离+1
    int n = gridSize;
    int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    //dist记录最短距离,-1表示未访问
    int** dist = (int**)malloc(N * sizeof(int*));
    Point* q = (Point*)malloc(n * n * sizeof(Point));
    int front = 0, rear = 0;

    for (int i = 0; i < n; i++){
        dist[i] = (int*)malloc(n * sizeof(int));
        memset(dist[i], -1, n * sizeof(int));
        for (int j = 0; j < n; j++){
            if(grid[i][j] == 1) {
                //陆地:距离0,多源入队
                dist[i][j] = 0;
                q[rear++] = (Point){i, j};
            }
        }
    }
    int maxDis = -1;
    //多源BFS扩散
    while(front < rear){
        Point cur = q[front++];
        int x = cur.x, y = cur.y;
        int d = dist[x][y];
        //四个方向
        for(int k = 0; k < 4; k++){
            int nx = x + dir[k][0];
            int ny = y + dir[k][1];

            //未访问的海洋
            if(nx >= 0 && nx < n && ny >= 0 && ny < n && dist[nx][ny] == -1){
                dist[nx][ny] = d + 1;
                if(dist[nx][ny] > maxDis){
                    maxDis = dist[nx][ny];
                }
                q[rear++] = (Point){nx, ny};
            }
        }
    }
    //释放内存空间
    for(int i = 0; i < n; i++) free(dist[i]);
    free(dist);
    free(q);
    return maxDis;
}

总结

所有陆地都是多源起点,距离初始为0,bfs逐层扩散,每走一层距离+1。