一起刷力扣之【1162. 地图分析】

1,055 阅读2分钟

这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

题目

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的。如果网格上只有陆地或者海洋,请返回 -1

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。

示例

image.png

输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2

image.png

输入: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

该题需要找到海洋跟最近一块陆地的最大距离,算是一道比较典型的广度优先搜索的题。我们可以将岛屿上所有的陆地都入队,通过该陆地为起点,将其周边的海洋都标记上距离,一圈圈的扩散开,直至最终的交汇处即可求得最远的距离。

  1. 找到所有陆地坐标点,将其入队
  2. 遍历队列中的坐标点,获取其周边的海洋左边,更新距离
  3. 将更新距离后的坐标点入队,重复 步骤2 直至队列为空
  4. 判断当前岛屿是否全为陆地或海洋
  5. 返回最后遍历到的坐标点,该点即为最长距离

1.gif

class Solution {
    public int maxDistance(int[][] grid) {
        Queue<Integer> queue = new LinkedList<>();
        int m = grid.length, n = grid[0].length;

        // 查找陆地,将其入队
        for(int x = 0; x < m; ++x){
            for(int y = 0; y < n; ++y){
                if(grid[x][y] == 1){
                    // 根据提示中的边界,优化两个坐标点为一个数值进行保存
                    queue.add(x * 1000 + y);
                }
            }
        }

        int idx = -1;
        // 陆地判断
        boolean flag = true;
        // 四周坐标
        int[][] arr = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        
        // 遍历队列中的坐标点
        while(!queue.isEmpty()){
            // 反序列化,得到坐标点
            idx = queue.poll();
            int x = idx / 1000;
            int y = idx % 1000;
            
            // 遍历四周
            for(int[] a : arr){
                int x0 = x + a[0];
                int y1 = y + a[1];
                // 非海洋则跳过
                if(x0 >= m || x0 < 0 || y1 >= n || y1 < 0 || grid[x0][y1] != 0){
                    continue;
                }
                
                // 更新并入队
                flag = false;
                grid[x0][y1] = grid[x][y] + 1;
                queue.add(x0 * 1000 + y1);
            }
        }

        // 海洋/陆地校验
        if(idx == -1 || flag){
            return -1;
        }

        // 得到最终的坐标点返回结果
        return grid[idx / 1000][idx % 1000] - 1;
    }
}

DP

我们也可以使用动态规划来解题,先从左上到右下,将海洋的坐标更新为距离左边陆地的长度,再从右下到左上遍历多一遍,修复一下海洋与右边陆地的长度,这样即可得到海洋在两块陆地中的一个中间值。

  1. 从左上到右下,计算出海洋与左上陆地之间的距离
  2. 从右下到左上,计算出海洋与右下陆地至今的距离
  3. 更新最长距离变量

2.gif

class Solution {
    public int maxDistance(int[][] grid) {
        boolean isLand = false;
        int m = grid.length, n = grid[0].length;

        // 从左上到右下,计算出海洋与左上陆地之间的距离
        for(int x = 0; x < m; ++x){
            for(int y = 0; y < n; ++y){
                // 当前坐标点为陆地,不需要计算,直接跳过
                if(grid[x][y] == 1){
                    isLand = true;
                    continue;
                }
                
                // 初始化
                if(grid[x][y] == 0){
                    grid[x][y] = m * n;
                }
                
                // 不是边界,则取前一个值做比较,得到当前海洋距离最近一块陆地的最长距离
                if(x > 0){
                    grid[x][y] = Math.min(grid[x][y], grid[x - 1][y] + 1);
                }
                if(y > 0){
                    grid[x][y] = Math.min(grid[x][y], grid[x][y - 1] + 1);
                }
            }
        }

        // 从右下到左上,计算出海洋与右下陆地至今的距离
        int result = 0;
        for(int x = m - 1; x >= 0; --x){
            for(int y = n - 1; y >= 0; --y){
                // 当前坐标点为陆地,不需要计算,直接跳过
                if(grid[x][y] == 1){
                    continue;
                }
                
                // 不是边界,则取前一个值做比较,得到当前海洋距离最近一块陆地的最长距离
                if(x < m - 1){
                    grid[x][y] = Math.min(grid[x][y], grid[x + 1][y] + 1);
                }
                if(y < m - 1){
                    grid[x][y] = Math.min(grid[x][y], grid[x][y + 1] + 1);
                }

                // 更新最长距离
                result = Math.max(result, grid[x][y]);
            }
        }
    
        // 全海洋返回 -1, 陆地返回最长距离
        return isLand ? result - 1 : -1;
    }
}