1. 问题描述与核心概念
这道题目要求我们计算一个由'1'(陆地)和'0'(水)组成的二维网格中岛屿的数量。岛屿被定义为被水包围的、由相邻陆地组成的区域,相邻指的是水平或垂直方向上相连的陆地。
举个例子,给定以下网格:
code复制[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
这个网格中有3个岛屿:左上角的4个'1'组成一个岛屿,中间的单个'1'是一个岛屿,右下角的两个'1'组成第三个岛屿。
2. 解题思路分析
2.1 问题分解
解决这个问题的关键在于如何有效地遍历网格并识别出相连的'1'组成的岛屿。我们需要考虑以下几点:
- 如何遍历整个网格?
- 遇到'1'时如何处理?
- 如何标记已经访问过的'1',避免重复计数?
- 如何探索与当前'1'相连的所有'1'?
2.2 算法选择
这个问题典型的解法是使用深度优先搜索(DFS)或广度优先搜索(BFS)。两种方法都能有效地探索相连的陆地:
- DFS:从一个'1'出发,递归地探索其上下左右的相邻格子
- BFS:使用队列来存储待探索的相邻格子
此外,还可以使用并查集(Union-Find)数据结构来解决,但实现起来相对复杂。
3. DFS解法详解
3.1 基本实现
DFS是最直观的解法。当我们遇到一个'1'时,就从这个位置开始进行DFS,将所有相连的'1'都标记为已访问(可以原地修改为'0'),这样就不会重复计数。
python复制def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
dfs(grid, i, j, rows, cols)
return count
def dfs(grid, i, j, rows, cols):
if i < 0 or j < 0 or i >= rows or j >= cols or grid[i][j] != '1':
return
grid[i][j] = '0' # 标记为已访问
dfs(grid, i+1, j, rows, cols)
dfs(grid, i-1, j, rows, cols)
dfs(grid, i, j+1, rows, cols)
dfs(grid, i, j-1, rows, cols)
3.2 复杂度分析
- 时间复杂度:O(M×N),其中M和N分别是网格的行数和列数。最坏情况下,我们需要访问每个格子一次。
- 空间复杂度:O(M×N),在最坏情况下(网格全是陆地),递归的深度可能达到M×N。
4. BFS解法详解
4.1 基本实现
BFS使用队列来存储待探索的相邻格子,避免了递归可能导致的栈溢出问题。
python复制from collections import deque
def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
queue = deque()
queue.append((i, j))
grid[i][j] = '0'
while queue:
x, y = queue.popleft()
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == '1':
grid[nx][ny] = '0'
queue.append((nx, ny))
return count
4.2 复杂度分析
- 时间复杂度:O(M×N),与DFS相同
- 空间复杂度:O(min(M,N)),在最坏情况下,队列中存储的格子数最多为网格的最短边
5. 并查集解法
5.1 并查集简介
并查集是一种树型的数据结构,用于处理一些不交集合的合并及查询问题。它支持两种操作:
- Find:确定元素属于哪个子集
- Union:将两个子集合并成一个集合
5.2 实现代码
python复制class UnionFind:
def __init__(self, grid):
rows, cols = len(grid), len(grid[0])
self.parent = [i for i in range(rows * cols)]
self.count = 0
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
self.count += 1
def find(self, i):
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
self.parent[rooty] = rootx
self.count -= 1
def numIslands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
uf = UnionFind(grid)
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
grid[i][j] = '0'
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
nx, ny = i + dx, j + dy
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == '1':
uf.union(i * cols + j, nx * cols + ny)
return uf.count
5.3 复杂度分析
- 时间复杂度:O(M×N×α(M×N)),其中α是反阿克曼函数,增长极其缓慢,可以认为是常数时间
- 空间复杂度:O(M×N),用于存储父节点数组
6. 优化与变种
6.1 方向数组的优化
我们可以将方向数组定义为类变量或全局变量,避免在每次调用时重新创建:
python复制DIRECTIONS = [(1,0), (-1,0), (0,1), (0,-1)]
6.2 边界检查的优化
可以将边界检查提取为单独的函数,提高代码可读性:
python复制def is_valid(grid, x, y):
return 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == '1'
6.3 岛屿的最大面积
这是一个常见的变种问题,要求找出最大的岛屿面积。可以在DFS/BFS时增加一个计数器:
python复制def dfs(grid, i, j):
if not is_valid(grid, i, j):
return 0
grid[i][j] = '0'
area = 1
for dx, dy in DIRECTIONS:
area += dfs(grid, i+dx, j+dy)
return area
7. 常见错误与调试技巧
7.1 忘记处理空输入
总是先检查输入是否为空:
python复制if not grid or not grid[0]:
return 0
7.2 方向处理不全
确保处理了所有四个方向(上、下、左、右),不要遗漏任何一个。
7.3 修改原数组
如果题目要求不能修改原数组,需要创建一个visited数组来记录访问状态:
python复制visited = [[False for _ in range(cols)] for _ in range(rows)]
7.4 递归深度问题
对于非常大的网格,DFS可能会导致栈溢出。这时应该使用BFS或迭代式DFS。
8. 实际应用场景
岛屿数量问题不仅仅是算法题,它在实际中有很多应用:
- 图像处理中的连通区域分析
- 地图服务中的地块识别
- 医学图像中的病灶区域检测
- 游戏开发中的地形生成与分析
理解这类问题的解法,有助于我们解决更复杂的现实世界问题。
