LeetCode 1020. 飞地的数量
题目描述


题意分析
网格中 1 表示陆地,0 表示海洋。只能沿上下左右相邻的陆地移动,统计无法走出网格边界的陆地格子数。这里统计的是格子数量,不是飞地连通块的数量。
解法:边界 BFS
核心思路
[!blue]
一条离开网格的陆地路径,最后必然经过某个边界陆地;反过来,能到达边界陆地的格子也一定能走出去。因此,“可以离开”恰好等价于“与边界上的陆地连通”。
从四条边上的所有陆地同时开始 BFS,沿四个方向清除与它们连通的陆地。搜索结束后,所有能离开的格子都已变成 0,剩下的 1 不可能有通向边界的陆地路径,逐个计数就是答案。
当前代码在出队时才把格子改成 0,所以角点或多个邻居可能重复加入同一坐标。出队时先跳过已经为 0 的格子,保证每格只展开一次。每格至多被四个邻居及边界收集加入常数次,重复项不会改变线性复杂度;网格本身同时充当访问标记。
解题步骤
- 扫描左右两列、上下两行,把边界陆地的坐标放入队列。
- 取出一个坐标;若已为 0,跳过这个重复项,否则将其改为 0。
- 检查上下左右四个邻居,把仍在网格内且值为 1 的格子加入队列。
- 队列耗尽后重新扫描网格,返回剩余值为 1 的格子数。
代码实现
class Solution {
public int numEnclaves(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
Deque<int[]> queue = new ArrayDeque<>();
for (int i = 0; i < m; i++) {
if (grid[i][0] == 1) {
queue.offer(new int[] {
i,
0
});
}
if (grid[i][n - 1] == 1) {
queue.offer(new int[] {
i,
n - 1
});
}
}
for (int j = 0; j < n; j++) {
if (grid[0][j] == 1) {
queue.offer(new int[] {
0,
j
});
}
if (grid[m - 1][j] == 1) {
queue.offer(new int[] {
m - 1,
j
});
}
}
int[] dx = {
1,
-1,
0,
0
};
int[] dy = {
0,
0,
1,
-1
};
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int x = cur[0];
int y = cur[1];
// 同一坐标可能重复入队,只在首次出队时展开。
if (grid[x][y] == 0) {
continue;
}
// 清除已确认能经陆地抵达边界的格子。
grid[x][y] = 0;
for (int k = 0; k < 4; k++) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
queue.offer(new int[] {
nx,
ny
});
}
}
}
int count = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
count++;
}
}
}
return count;
}
}
func numEnclaves(grid [][]int) int {
m := len(grid)
n := len(grid[0])
queue := make([][2]int, 0)
for i := 0; i < m; i++ {
if grid[i][0] == 1 {
queue = append(queue, [2]int{
i,
0,
})
}
if grid[i][n-1] == 1 {
queue = append(queue, [2]int{
i,
n - 1,
})
}
}
for j := 0; j < n; j++ {
if grid[0][j] == 1 {
queue = append(queue, [2]int{
0,
j,
})
}
if grid[m-1][j] == 1 {
queue = append(queue, [2]int{
m - 1,
j,
})
}
}
dx := []int{
1,
-1,
0,
0,
}
dy := []int{
0,
0,
1,
-1,
}
head := 0
for head < len(queue) {
cur := queue[head]
head++
x, y := cur[0], cur[1]
// 同一坐标可能重复入队,只在首次出队时展开。
if grid[x][y] == 0 {
continue
}
// 清除已确认能经陆地抵达边界的格子。
grid[x][y] = 0
for k := 0; k < 4; k++ {
nx := x + dx[k]
ny := y + dy[k]
if nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1 {
queue = append(queue, [2]int{
nx,
ny,
})
}
}
}
count := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
count++
}
}
}
return count
}
复杂度分析
- 时间复杂度:$O(mn)$,其中
m、n为网格行列数;每个格子最多展开一次,重复入队次数也有常数上界。- 空间复杂度:$O(mn)$ 上界,队列占用;Go 下标队列还保留已经处理的坐标。
关键点总结
[!green]
- 边界连通性按四个方向,斜对角不相连。
- 单行、单列全部在边界上,答案零。
- 边界收集中的角点、单行或单列可能重复入队,出队检查负责跳过已处理项。
易错点总结
[!yellow]
- 只收集上下边会漏掉左右出口。
- 当前出队标记写法去掉重复检查,会反复展开同一格。
- 用八方向搜索,会把仅斜接边界的内部陆地错误清除。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 130. 被围绕的区域 | 中等 | 同样从边界清除可逃离连通区域,原题翻转内部区域,本题统计剩余陆地格子数。 |
| 200. 岛屿数量 | 中等 | 普通连通遍历是基础,本题额外区分分量是否连接到边界。 |
| 695. 岛屿的最大面积 | 中等 | 用洪水填充标记网格连通分量;本题从边界排除可逃离的陆地,该题统计各分量面积并取最大。 |
| 733. 图像渲染 | 简单 | 用洪水填充标记网格连通分量;本题从边界排除可逃离的陆地,该题修改起点所属的同色分量。 |
| 1254. 统计封闭岛屿的数目 | 中等 | 用洪水填充标记网格连通分量;本题从边界排除可逃离的陆地,该题排除接触边界的零分量。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!