LeetCode 1162. 地图分析
题目描述


题意分析
网格中一表示陆地,零表示海洋。先对每一个海洋格计算它到最近陆地的曼哈顿距离,再从这些最近距离中取最大值,返回最远离任何陆地的海洋格的距离。
要取的是“到最近陆地的距离”的最大值,不是任意一对海陆之间的最大距离。距离按上下左右移动的步数计算,每次移动一格算一步,不走对角线;如果没有陆地或没有海洋,按题意返回
-1。
解法:多源广度优先搜索
核心思路
[!blue]
逐个海洋格寻找最近陆地会反复搜索相同区域。反过来,把全部陆地同时放进 BFS 的初始队列,视为距离零的一层,再一层层向相邻海洋扩散,就可以一次得到所有海洋格到最近陆地的距离。
BFS 按距离从小到大处理。若某个海洋格第一次从距离
d的格子被发现,就存在一条长为d + 1的海陆路径;如果它还有更短的路径,那条路径的前一格必然在更早的层被处理,早就应该发现它。因此第一次发现对应的距离就是最近距离,而不必区分是由哪片陆地到达。每层处理之前先固定当前队列大小,保证新加入的格子都留到下一层。距离变量从
-1开始,每处理一层先加一,所以第一次处理全部陆地时距离恰好为零;之后每扩散一层,距离增加一。队列耗尽时,最后一层就是海洋格中最大的最近距离。同一个海洋格可能被多个来源发现,必须在入队时就把它标记为已访问。代码直接将其改成一,既作为访问标记,也避免再次入队;因此会修改输入网格。所有陆地已经在初始队列中,后续只加入尚未访问的海洋。
网格没有不可通过的障碍,只要同时存在海陆,全部海洋最终都能被扩散到。全海或全陆要在搜索前单独返回
-1,否则没有有效的海洋到陆地距离可以取最大值。
解题步骤
- 扫描网格,把所有陆地坐标加入队列,并记录是否存在海洋与陆地。
- 任意一种不存在时返回
-1,否则令dist = -1。- 每轮固定当前待处理层的大小,并将
dist加一。- 处理本层每个格子的四个相邻位置,跳过越界、陆地和已经访问过的位置。
- 将新发现的海洋立刻标记为一,再加入队列,留给下一层处理。
- 队列为空后返回
dist,它是最晚扩散到的海洋层距离。
代码实现
class Solution {
public int maxDistance(int[][] grid) {
int n = grid.length;
Queue<int[]> queue = new ArrayDeque<>();
boolean hasLand = false;
boolean hasWater = false;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
queue.offer(new int[] {
i,
j
});
hasLand = true;
} else {
hasWater = true;
}
}
}
if (!hasLand || !hasWater) {
return -1;
}
int dist = -1;
int[][] dirs = {
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
};
while (!queue.isEmpty()) {
// 固定这一层数量,新发现格子属于下一层。
int size = queue.size();
dist++;
for (int i = 0; i < size; i++) {
int[] cur = queue.poll();
for (int[] dir : dirs) {
int nr = cur[0] + dir[0];
int nc = cur[1] + dir[1];
if (nr < 0 || nr >= n || nc < 0 || nc >= n) {
continue;
}
if (grid[nr][nc] == 1) {
continue;
}
// 入队时标记,多个陆地来源不会重复发现同一格。
grid[nr][nc] = 1;
queue.offer(new int[] {
nr,
nc
});
}
}
}
return dist;
}
}
func maxDistance(grid [][]int) int {
n := len(grid)
queue := make([][2]int, 0)
hasLand := false
hasWater := false
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
queue = append(queue, [2]int{
i,
j,
})
hasLand = true
} else {
hasWater = true
}
}
}
if !hasLand || !hasWater {
return -1
}
dirs := [][2]int{
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
}
dist := -1
for head := 0; head < len(queue); {
// 只计尚未处理的当前层,不包含历史队列前缀。
size := len(queue) - head
dist++
for i := 0; i < size; i++ {
cur := queue[head]
head++
for _, d := range dirs {
nr := cur[0] + d[0]
nc := cur[1] + d[1]
if nr < 0 || nr >= n || nc < 0 || nc >= n {
continue
}
if grid[nr][nc] == 1 {
continue
}
// 入队时标记,多个陆地来源不会重复发现同一格。
grid[nr][nc] = 1
queue = append(queue, [2]int{
nr,
nc,
})
}
}
}
return dist
}
复杂度分析
- 时间复杂度:
O(n²)。网格有n²个格子,每个格子最多入队一次,每次检查四个方向。- 空间复杂度:
O(n²)。队列最坏保存线性数量的网格坐标,访问标记直接复用并改写原网格。
关键点总结
[!green]
- 多个陆地同时作为零距离源点,第一次到达才表示到所有陆地的最近距离。
- BFS 层号给出每个海洋格的最小距离,最后一层给出这些最小距离的最大值。
- 入队即标记,使不同方向和不同陆地不会重复加入同一格。
- 固定每层数量才能把当前层与新发现的下一层分开。
易错点总结
[!yellow]
- 只从一块陆地开始:得到的是到该起点的距离,未必是到最近陆地的距离。
- 把陆地初始层记为一:所有海洋距离都会多算一步,陆地距离应为零。
- 遍历本层时动态扩大处理数量:会把下一层提前处理,层号不再对应真实距离。
- 出队才标记:同一格可能在出队前被多个邻居重复加入,造成冗余扩展。
- Go 队列计入已经处理的历史前缀:当前层长度应取
len(queue) - head,而不是队列总长度。- 忽略原网格被改写:一同时表示原陆地与已访问海洋,搜索结束后原始海陆信息不再保留。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 542. 01 矩阵 | 中等 | 多源BFS得到每格到最近陆地或0的距离,本题只取海洋格距离最大值。 |
| 934. 最短的桥 | 中等 | 两题都在岛屿间扩散,但最短桥取两个岛的最小间距,本题取海洋到最近陆地的最大距离。 |
| 994. 腐烂的橘子 | 中等 | 从多个起点同时进行逐层广度优先搜索;本题从全部陆地求海洋的最近距离,该题模拟腐烂波及相邻橘子的分钟数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!