文章目录
题目
标题和出处
标题:地图分析
出处:1162. 地图分析
难度
4 级
题目描述
要求
给定一个 n × n \texttt{n} \times \texttt{n} n×n 的网格 grid \texttt{grid} grid,网格中的值都是 0 \texttt{0} 0 或 1 \texttt{1} 1,其中 0 \texttt{0} 0 代表水域, 1 \texttt{1} 1 代表陆地。要求找出一个水域单元格,这个水域单元格到离它最近的陆地单元格的距离最大,并返回该距离。如果网格上只有陆地或者水域,返回 -1 \texttt{-1} -1。
这道题中的距离是曼哈顿距离:单元格 (x 0 , y 0 ) \texttt{(x}\texttt{0}\texttt{, y}\texttt{0}\texttt{)} (x0, y0) 和 (x 1 , y 1 ) \texttt{(x}\texttt{1}\texttt{, y}\texttt{1}\texttt{)} (x1, y1) 之间的距离是 ∣ x 0 − x 1 ∣ + ∣ y 0 − y 1 ∣ |\texttt{x}\texttt{0} - \texttt{x}\texttt{1}| + |\texttt{y}\texttt{0} - \texttt{y}\texttt{1}| ∣x0−x1∣+∣y0−y1∣。
示例
示例 1:

输入: grid = [[1,0,1],[0,0,0],[1,0,1]] \texttt{grid = [[1,0,1],[0,0,0],[1,0,1]]} grid = [[1,0,1],[0,0,0],[1,0,1]]
输出: 2 \texttt{2} 2
解释:单元格 (1, 1) \texttt{(1, 1)} (1, 1) 和所有陆地的距离都达到最大,最大距离为 2 \texttt{2} 2。
示例 2:

输入: grid = [[1,0,0],[0,0,0],[0,0,0]] \texttt{grid = [[1,0,0],[0,0,0],[0,0,0]]} grid = [[1,0,0],[0,0,0],[0,0,0]]
输出: 4 \texttt{4} 4
解释:单元格 (2, 2) \texttt{(2, 2)} (2, 2) 和所有陆地的距离都达到最大,最大距离为 4 \texttt{4} 4。
数据范围
- n = grid.length \texttt{n} = \texttt{grid.length} n=grid.length
- n = grid[i].length \texttt{n} = \texttt{grid[i].length} n=grid[i].length
- 1 ≤ n ≤ 100 \texttt{1} \le \texttt{n} \le \texttt{100} 1≤n≤100
- grid[i][j] \texttt{grid[i][j]} grid[i][j] 为 0 \texttt{0} 0 或 1 \texttt{1} 1
解法一
思路和算法
如果网格中的所有单元格都是水域或都是陆地,则返回 − 1 -1 −1。以下只考虑同时有水域和陆地的情况。
由于广度优先搜索的遍历顺序是和起始点的距离递增的顺序,因此可以使用广度优先搜索计算每个水域单元格到最近的陆地单元格的距离,做法是从所有的陆地单元格出发执行多源广度优先搜索,搜索过程中维护一个与网格相同大小的二维数组,用于记录网格中的每个水域单元格到最近的陆地单元格的距离,该二维数组称为距离数组。
对于网格中的每个水域单元格,如果在第 k k k 轮被首次访问到,则当前水域单元格到最近的陆地单元格的距离是 k k k。理由如下:如果当前水域单元格到最近的陆地单元格的距离小于 k k k,则从最近的陆地单元格开始访问到当前水域单元格的轮数一定小于 k k k,因此当前水域单元格被首次访问到的轮数小于 k k k,与当前水域单元格在第 k k k 轮被首次访问到矛盾。因此网格中的每个水域单元格被首次访问到的轮数即为到最近的陆地单元格的距离。
遍历结束之后,即可得到每个水域单元格到最近的陆地单元格的距离,以及水域单元格到最近的陆地单元格的最大距离。
实现方面有以下两点说明。
-
对于每个单元格需要向四个方向遍历,可以创建方向数组实现四个方向的遍历。
-
广度优先搜索需要记录每个单元格是否被访问过,这道题由于需要计算每个单元格到最近的陆地单元格的距离,因此可以根据距离数组中的元素判断每个单元格是否被访问过。具体做法是,将网格中的每个陆地单元格对应的距离数组中的元素初始化为 0 0 0,将网格中的每个水域单元格对应的距离数组中的元素初始化为无穷大,则距离数组中的值为无穷大的元素表示该单元格未访问。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
static final int INFINITY = Integer.MAX_VALUE / 2;
public int maxDistance(int[][] grid) {
int n = grid.length;
int[][] distances = new int[n][n];
Queue<int[]> queue = new ArrayDeque<int[]>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
distances[i][j] = INFINITY;
} else {
queue.offer(new int[]{i, j});
}
}
}
if (queue.isEmpty() || queue.size() == n * n) {
return -1;
}
int maxDist = 0;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
maxDist = Math.max(maxDist, distances[row][col]);
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n && distances[newRow][newCol] == INFINITY) {
distances[newRow][newCol] = distances[row][col] + 1;
queue.offer(new int[]{newRow, newCol});
}
}
}
return maxDist;
}
}
复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格 grid \textit{grid} grid 的边长。广度优先搜索最多需要访问每个单元格一次。
-
空间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格 grid \textit{grid} grid 的边长。距离数组和队列需要 O ( n 2 ) O(n^2) O(n2) 的空间。
解法二
预备知识
该解法涉及到动态规划。
动态规划是求解决策过程最优化的过程,其核心思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。动态规划适用于存在重叠子问题的情况,当一个子问题被解决时,将该子问题的答案记录在表格中,下次遇到相同子问题时不需要重复计算,而是可以直接从表格中得到该子问题的答案,从而实现降低时间复杂度的目的。
动态规划的三个要素是状态定义、边界情况和状态转移方程。首先需要定义状态的含义,然后直接得到边界情况中的每个状态的结果,最后使用状态转移方程计算所有状态的结果。
思路和算法
如果网格中的所有单元格都是水域或都是陆地,则返回 − 1 -1 −1。以下只考虑同时有水域和陆地的情况。
用 distances \textit{distances} distances 表示距离数组。对于 0 ≤ i < n 0 \le i < n 0≤i<n 和 0 ≤ j < n 0 \le j < n 0≤j<n,如果 grid [ i ] [ j ] \textit{grid}[i][j] grid[i][j] 是陆地单元格则 distances [ i ] [ j ] = 0 \textit{distances}[i][j] = 0 distances[i][j]=0,如果 grid [ i ] [ j ] \textit{grid}[i][j] grid[i][j] 是水域单元格则 distances [ i ] [ j ] \textit{distances}[i][j] distances[i][j] 为网格的第 i i i 行第 j j j 列到最近的陆地单元格的距离。
网格中的每个水域单元格都可以经过竖直方向移动 x x x 次和水平方向移动 y y y 次到达最近的陆地单元格(其中 x x x 和 y y y 是非负整数),竖直方向可以向上或向下移动,水平方向可以向左或向右移动。因此可以使用动态规划计算。
距离数组中的元素是动态规划的状态,动态规划的边界情况是当 grid [ i ] [ j ] \textit{grid}[i][j] grid[i][j] 是陆地单元格时 distances [ i ] [ j ] = 0 \textit{distances}[i][j] = 0 distances[i][j]=0。
当 grid [ i ] [ j ] \textit{grid}[i][j] grid[i][j] 是水域单元格时,动态规划的状态转移方程如下:
distances [ i ] [ j ] = min ( distances [ i − 1 ] [ j ] , distances [ i + 1 ] [ j ] , distances [ i ] [ j − 1 ] , distances [ i ] [ j + 1 ] ) + 1 \textit{distances}[i][j] = \min(\textit{distances}[i - 1][j], \textit{distances}[i + 1][j], \textit{distances}[i][j - 1], \textit{distances}[i][j + 1]) + 1 distances[i][j]=min(distances[i−1][j],distances[i+1][j],distances[i][j−1],distances[i][j+1])+1
计算状态 distances [ i ] [ j ] \textit{distances}[i][j] distances[i][j] 时,需要考虑行下标 i i i 和列下标 j j j 分别从小到大遍历和从大到小遍历的顺序,计算如下。
-
行下标 i i i 从小到大遍历,列下标 j j j 从小到大遍历, distances [ i ] [ j ] = min ( distances [ i ] [ j ] , distances [ i − 1 ] [ j ] , distances [ i ] [ j − 1 ] ) \textit{distances}[i][j] = \min(\textit{distances}[i][j], \textit{distances}[i - 1][j], \textit{distances}[i][j - 1]) distances[i][j]=min(distances[i][j],distances[i−1][j],distances[i][j−1])。
-
行下标 i i i 从小到大遍历,列下标 j j j 从大到小遍历, distances [ i ] [ j ] = min ( distances [ i ] [ j ] , distances [ i ] [ j + 1 ] ) \textit{distances}[i][j] = \min(\textit{distances}[i][j], \textit{distances}[i][j + 1]) distances[i][j]=min(distances[i][j],distances[i][j+1])。
-
行下标 i i i 从大到小遍历,列下标 j j j 从小到大遍历, distances [ i ] [ j ] = min ( distances [ i ] [ j ] , distances [ i + 1 ] [ j ] , distances [ i ] [ j − 1 ] ) \textit{distances}[i][j] = \min(\textit{distances}[i][j], \textit{distances}[i + 1][j], \textit{distances}[i][j - 1]) distances[i][j]=min(distances[i][j],distances[i+1][j],distances[i][j−1])。
-
行下标 i i i 从大到小遍历,列下标 j j j 从大到小遍历, distances [ i ] [ j ] = min ( distances [ i ] [ j ] , distances [ i ] [ j + 1 ] ) \textit{distances}[i][j] = \min(\textit{distances}[i][j], \textit{distances}[i][j + 1]) distances[i][j]=min(distances[i][j],distances[i][j+1])。
上述计算中存在重复计算,其实只需要保留两项:行下标和列下标都从小到大遍历,以及行下标和列下标都从大到小遍历。
计算所有的状态之后,遍历距离数组 distances \textit{distances} distances 并得到距离数组中的最大值,即为水域单元格到最近的陆地单元格的最大距离。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
static final int INFINITY = Integer.MAX_VALUE / 2;
public int maxDistance(int[][] grid) {
int n = grid.length;
int[][] distances = new int[n][n];
boolean flag0 = false, flag1 = false;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
distances[i][j] = INFINITY;
flag0 = true;
} else {
flag1 = true;
}
}
}
if (!flag0 || !flag1) {
return -1;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
if (i > 0) {
distances[i][j] = Math.min(distances[i][j], distances[i - 1][j] + 1);
}
if (j > 0) {
distances[i][j] = Math.min(distances[i][j], distances[i][j - 1] + 1);
}
}
}
}
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
if (grid[i][j] == 0) {
if (i < n - 1) {
distances[i][j] = Math.min(distances[i][j], distances[i + 1][j] + 1);
}
if (j < n - 1) {
distances[i][j] = Math.min(distances[i][j], distances[i][j + 1] + 1);
}
}
}
}
int maxDist = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
maxDist = Math.max(maxDist, distances[i][j]);
}
}
return maxDist;
}
}
复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格 grid \textit{grid} grid 的边长。初始化距离数组需要 O ( n 2 ) O(n^2) O(n2) 的时间,计算距离数组需要遍历网格 grid \textit{grid} grid 两次,时间是 O ( n 2 ) O(n^2) O(n2)。
-
空间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格 grid \textit{grid} grid 的边长。距离数组需要 O ( n 2 ) O(n^2) O(n2) 的空间。