1. 问题背景与理解
今天我们来拆解LeetCode第1254题"统计封闭岛屿的数目"。这是一道经典的图遍历问题,考察我们对网格类问题的处理能力。题目描述如下:
给定一个由0(陆地)和1(水)组成的二维网格,一个封闭岛屿是由0组成的区域,这个区域完全被1包围(意味着边界上的0不算封闭岛屿)。我们需要统计网格中封闭岛屿的数量。
举个例子:
code复制输入: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
在这个例子中,有两个封闭岛屿(中间的"0"区域),而边缘的"0"不算封闭岛屿。
2. 解题思路分析
2.1 问题分解
要解决这个问题,我们需要明确几个关键点:
- 什么是封闭岛屿?它必须完全由1包围,不与网格边界相连
- 如何判断一个岛屿是否封闭?需要遍历整个岛屿区域,检查是否接触边界
- 如何避免重复计数?需要标记已经访问过的陆地单元格
2.2 算法选择
这类网格遍历问题通常有两种经典解法:
- 深度优先搜索(DFS)
- 广度优先搜索(BFS)
两种方法在时间复杂度上都是O(m×n),其中m和n分别是网格的行数和列数。DFS的实现通常更简洁,而BFS可以避免递归导致的栈溢出问题。这里我们选择DFS来实现。
2.3 边界处理技巧
一个关键技巧是:我们可以先处理所有边界上的陆地(0),将它们标记为已访问(比如改为1),因为这些陆地不可能构成封闭岛屿。这样剩下的陆地就都是潜在的封闭岛屿组成部分。
3. 详细实现步骤
3.1 预处理边界
首先,我们遍历网格的四条边:
- 第一行和最后一行
- 第一列和最后一列
对于这些边界上的0,我们进行DFS/BFS,将它们全部标记为非陆地(比如改为1)。
python复制def closedIsland(grid):
if not grid:
return 0
m, n = len(grid), len(grid[0])
# 预处理边界上的陆地
for i in range(m):
for j in range(n):
if i == 0 or j == 0 or i == m-1 or j == n-1:
if grid[i][j] == 0:
dfs(grid, i, j)
3.2 DFS实现
DFS函数用于遍历并标记相连的陆地:
python复制def dfs(grid, i, j):
if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]):
return
if grid[i][j] != 0:
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)
3.3 统计封闭岛屿
预处理完边界后,我们遍历网格内部的所有单元格,遇到0时就启动DFS,并增加岛屿计数:
python复制count = 0
for i in range(1, m-1):
for j in range(1, n-1):
if grid[i][j] == 0:
dfs(grid, i, j)
count += 1
return count
4. 完整代码实现
将上述步骤组合起来,完整的Python解决方案如下:
python复制class Solution:
def closedIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0
m, n = len(grid), len(grid[0])
def dfs(i, j):
if i < 0 or j < 0 or i >= m or j >= n:
return
if grid[i][j] != 0:
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):
for j in range(n):
if i == 0 or j == 0 or i == m-1 or j == n-1:
if grid[i][j] == 0:
dfs(i, 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
5. 复杂度分析
- 时间复杂度:O(m×n),其中m和n分别是网格的行数和列数。我们最多会访问每个单元格两次(边界处理和内部统计)。
- 空间复杂度:O(m×n),主要是递归调用栈的空间。在最坏情况下(整个网格都是陆地),递归深度可能达到m×n。
6. 优化与变种
6.1 使用BFS实现
为了避免递归深度过大导致的栈溢出问题,我们可以改用BFS实现:
python复制from collections import deque
def closedIsland(grid):
if not grid:
return 0
m, n = len(grid), len(grid[0])
directions = [(1,0), (-1,0), (0,1), (0,-1)]
def bfs(i, j):
queue = deque([(i, j)])
grid[i][j] = 1
while queue:
x, y = queue.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == 0:
grid[nx][ny] = 1
queue.append((nx, ny))
# 处理边界
for i in range(m):
for j in range(n):
if (i == 0 or j == 0 or i == m-1 or j == n-1) and grid[i][j] == 0:
bfs(i, j)
# 统计封闭岛屿
count = 0
for i in range(1, m-1):
for j in range(1, n-1):
if grid[i][j] == 0:
bfs(i, j)
count += 1
return count
6.2 不修改原网格的解法
如果题目要求不能修改原网格,我们可以使用额外的visited数组来记录访问状态:
python复制def closedIsland(grid):
if not grid:
return 0
m, n = len(grid), len(grid[0])
visited = [[False for _ in range(n)] for _ in range(m)]
def dfs(i, j):
if i < 0 or j < 0 or i >= m or j >= n:
return False
if grid[i][j] == 1 or visited[i][j]:
return True
visited[i][j] = True
isClosed = True
for di, dj in [(1,0), (-1,0), (0,1), (0,-1)]:
if not dfs(i+di, j+dj):
isClosed = False
return isClosed
count = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 0 and not visited[i][j]:
if dfs(i, j):
count += 1
return count
7. 常见错误与调试技巧
7.1 边界条件处理
常见错误包括:
- 忘记处理网格为空的情况
- 边界条件判断错误(比如只处理了四个角而忽略了整个边界)
- 行列索引混淆(特别是在处理二维数组时)
调试技巧:
- 打印网格在处理前后的状态
- 使用小规模的测试用例手动验证
7.2 递归深度问题
对于非常大的网格,DFS递归可能导致栈溢出。解决方法:
- 改用BFS实现
- 使用迭代式DFS(手动维护栈)
7.3 方向遍历顺序
四个方向的遍历顺序不影响正确性,但会影响访问顺序。常见的四种方向定义方式:
python复制# 上下左右
directions = [(-1,0), (1,0), (0,-1), (0,1)]
# 或者右左下上
directions = [(0,1), (0,-1), (1,0), (-1,0)]
8. 实际应用场景
这类网格遍历问题在实际中有广泛的应用:
- 图像处理中的连通区域分析
- 地图应用中的岛屿识别
- 游戏开发中的区域填充算法
- 电路板设计中的短路检测
理解这类问题的解法可以帮助我们解决许多类似的现实问题。比如在图像处理中,我们可能需要找出所有被特定颜色包围的区域;在地图应用中,可能需要识别出完全被水域包围的陆地等。
9. 类似题目推荐
为了巩固这类问题的解法,可以尝试以下LeetCode题目:
-
- Number of Islands(岛屿数量)
-
- Max Area of Island(岛屿的最大面积)
-
- Surrounded Regions(被围绕的区域)
-
- Number of Enclaves(飞地的数量)
-
- Number of Distinct Islands(不同岛屿的数量)
这些题目都采用了类似的网格遍历技术,但在具体条件和要求上有所不同,是很好的练习材料。
