# [1162] 地图分析

你现在手里有一份大小为 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

这道题有一点像 FloodFill 问题,遍历每一个海洋点,通过方向去寻找岛屿。

而同时,它又是一道求图最短距离的问题,因此需要使用到 BFS 去做查找。

根据专题中对这两类题的了解,我们可以写出如下解法:

通过遍历每一个海洋节点,再通过 BFS 方向遍历找到离海洋最近的陆地节点,算出每一个海洋节点的最近距离,取最大值返回。

function maxDistance(grid: number[][]): number {
  const height = grid.length;
  const width = grid[0].length;
  const direction = [
    [-1, 0],
    [1, 0],
    [0, -1],
    [0, 1]
  ];

  let res: number = -1;
  for (let i = 0; i < height; i++) {
    for (let j = 0; j < width; j++) {
      if (grid[i][j] === 0) {
        const distance = bfs(i, j);
        res = Math.max(distance, res);
      }
    }
  }
  return res;

  function bfs(row: number, col: number) {
    const visited: boolean[][] = Array(height)
      .fill(0)
      .map(x => Array(width).fill(false));

    const queue: [row: number, col: number][] = [];
    queue.unshift([row, col]);
    visited[row][col] = true;

    while (queue.length > 0) {
      const [currRow, currCol] = queue.pop();
      for (let [a, b] of direction) {
        const x: number = currRow + a;
        const y: number = currCol + b;

        // 越界或已访问过,跳过
        if (x < 0 || x >= height || y < 0 || y >= width) {
          continue;
        }
        if (visited[x][y]) continue;

        // 当第一次搜索到陆地,此时一定为最近陆地,返回距离
        if (grid[x][y] === 1) {
          return Math.abs(x - row) + Math.abs(y - col);
        }

        // 入队并记录
        queue.unshift([x, y]);
        visited[x][y] = true;
      }
    }
    return -1;
  }
}

而这样写从代码上看就十分复杂,而且直接提交的话有可能会提示超时。

接下来我们分析一下复杂度。发现最坏时间复杂度居然达到了 O(n^(2*2)) = O(n^4) 的程度。这显然有点复杂了。

而换一个角度想想,如果我们不从海洋的角度出发,而是从陆地的角度出发呢?

我们找到每一个陆地节点,并同时对陆地节点进行BFS扩散,当第一次经过某个海洋节点,就标记一次距离最近陆地的距离。

实际上,这就是多源 BFS。一个同时从多点进行BFS扩散,帮助我们缩短找到目标节点的时间和复杂度的方式。

而显然,最后访问到的海洋节点,则一定是离陆地最远的海洋。返回最大的标记距离即可。

function maxDistance(grid: number[][]): number {
  const height = grid.length;
  const width = grid[0].length;
  const direction = [
    [-1, 0],
    [1, 0],
    [0, -1],
    [0, 1]
  ];

  let res: number = -1;

  // 将所有陆地节点入队
  const queue: [row: number, col: number][] = [];
  for (let i = 0; i < height; i++) {
    for (let j = 0; j < width; j++) {
      if (grid[i][j] === 1) {
        queue.push([i, j]);
      }
    }
  }

  // 没有陆地或者没有海洋
  if (queue.length === 0 || queue.length === height * width) {
    return -1;
  }

  // 多源 BFS,从所有岛屿开始向外 BFS
  while (queue.length > 0) {
    const [x, y] = queue.pop();

    for (let [a, b] of direction) {
      const newX = x + a;
      const newY = y + b;

      // 越界,跳过
      if (newX < 0 || newX >= height || newY < 0 || newY >= width) {
        continue;
      }

      // 当前节点为陆地,或是已经被遍历过,跳过
      if (grid[newX][newY] !== 0) {
        continue;
      }

      // 新的海洋,距离为上一层 BFS 的节点距离 + 1
      const distance = grid[x][y] + 1;
      grid[newX][newY] = distance;
      res = Math.max(res, distance);

      queue.unshift([newX, newY]);
    }
  }

  // 由于距离从陆地的1开始记录,而显然陆地距离自己为0,因此结果要去掉
  return res - 1;
}