Medium
题目描述
给你一个大小为 m x n 的二进制网格 grid 。网格中 0 表示土地,1 表示水域。
岛屿是由四面连通的 0 组成的一个连通分量,封闭岛屿是一个完全由 1 包围(左、上、右、下)的岛屿。
请返回封闭岛屿的数目。
示例 1:
输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
输出:2
解释:灰色区域的岛屿是封闭岛屿,因为这些岛屿完全被水域包围(即被 1 区域包围)。
示例 2:
输入:grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]]
输出:1
示例 3:
输入:grid = [[1,1,1,1,1,1,1],
[1,0,0,0,0,0,1],
[1,0,1,1,1,0,1],
[1,0,1,0,1,0,1],
[1,0,1,1,1,0,1],
[1,0,0,0,0,0,1],
[1,1,1,1,1,1,1]]
输出:2
提示:
1 <= grid.length, grid[0].length <= 1000 <= grid[i][j] <= 1- 排除连接到边界的 0 组,因为它们不是封闭岛屿。
- 返回网格中 0 的连通分量数目。
解题思路
这道题的核心思路是识别出真正"封闭"的岛屿。一个岛屿要成为封闭岛屿,必须完全被水域包围,不能触及网格边界。
解题思路分为两个关键步骤:
第一步:排除边界岛屿 首先遍历网格的四条边界,对于边界上所有值为 0 的位置,使用 DFS 或 BFS 将其所在的整个连通分量标记为已访问(可以修改为 1 或使用 visited 数组)。这样做的原因是:任何与边界相连的岛屿都不可能是封闭的,因为它们可以通过边界"逃逸"到网格外部。
第二步:计算剩余封闭岛屿 完成边界处理后,再次遍历整个网格的内部区域。每当遇到一个未访问的 0 时,说明发现了一个真正的封闭岛屿,使用 DFS 遍历整个岛屿并标记为已访问,同时将答案计数加 1。
这种方法巧妙地利用了"排除法"的思想:与其直接判断一个岛屿是否封闭(需要在遍历过程中检查是否触及边界),不如先排除所有不封闭的岛屿,剩下的自然就是封闭岛屿。
时间复杂度为 O(m×n),因为每个格子最多被访问两次。空间复杂度为 O(m×n),主要是递归调用栈的开销。
代码实现
class Solution {
public:
int closedIsland(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 排除边界上的岛屿
for (int i = 0; i < m; i++) {
if (grid[i][0] == 0) dfs(grid, i, 0);
if (grid[i][n-1] == 0) dfs(grid, i, n-1);
}
for (int j = 0; j < n; j++) {
if (grid[0][j] == 0) dfs(grid, 0, j);
if (grid[m-1][j] == 0) dfs(grid, m-1, j);
}
// 计算剩余的封闭岛屿
int count = 0;
for (int i = 1; i < m-1; i++) {
for (int j = 1; j < n-1; j++) {
if (grid[i][j] == 0) {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
private:
void dfs(vector<vector<int>>& grid, int i, int j) {
if (i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] == 1) {
return;
}
grid[i][j] = 1; // 标记为已访问
dfs(grid, i+1, j);
dfs(grid, i-1, j);
dfs(grid, i, j+1);
dfs(grid, i, j-1);
}
};
class Solution:
def closedIsland(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] == 1:
return
grid[i][j] = 1 # 标记为已访问
dfs(i+1, j)
dfs(i-1, j)
dfs(i, j+1)
dfs(i, j-1)
# 排除边界上的岛屿
for i in range(m):
if grid[i][0] == 0:
dfs(i, 0)
if grid[i][n-1] == 0:
dfs(i, n-1)
for j in range(n):
if grid[0][j] == 0:
dfs(0, j)
if grid[m-1][j] == 0:
dfs(m-1, j)
# 计算剩余的封闭岛屿
count = 0
for i in range(1, m-1):
for j in range(1, n-1):
if grid[i][j] == 0:
dfs(i, j)
count += 1
return count
public class Solution {
public int ClosedIsland(int[][] grid) {
int m = grid.Length, n = grid[0].Length;
// 排除边界上的岛屿
for (int i = 0; i < m; i++) {
if (grid[i][0] == 0) DFS(grid, i, 0);
if (grid[i][n-1] == 0) DFS(grid, i, n-1);
}
for (int j = 0; j < n; j++) {
if (grid[0][j] == 0) DFS(grid, 0, j);
if (grid[m-1][j] == 0) DFS(grid, m-1, j);
}
// 计算剩余的封闭岛屿
int count = 0;
for (int i = 1; i < m-1; i++) {
for (int j = 1; j < n-1; j++) {
if (grid[i][j] == 0) {
DFS(grid, i, j);
count++;
}
}
}
return count;
}
private void DFS(int[][] grid, int i, int j) {
if (i < 0 || i >= grid.Length || j < 0 || j >= grid[0].Length || grid[i][j] == 1) {
return;
}
grid[i][j] = 1; // 标记为已访问
DFS(grid, i+1, j);
DFS(grid, i-1, j);
DFS(grid, i, j+1);
DFS(grid, i, j-1);
}
}
var closedIsland = function(grid) {
const m = grid.length, n = grid[0].length;
const dfs = (i, j) => {
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j]
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(m×n) | 需要遍历整个网格,每个格子最多被访问两次 |
| 空间复杂度 | O(m×n) | 递归调用栈的最大深度为网格大小 |