1. 计数孤岛问题概述
计数孤岛(Counting Islands)是计算机科学中一个经典的图论问题,也是算法面试中的高频考题。这个问题要求我们统计二维矩阵中由相邻的"1"(陆地)组成的连通区域数量,其中"0"表示水域。这个问题看似简单,却蕴含着深度优先搜索(DFS)、广度优先搜索(BFS)和并查集(Union-Find)等核心算法的精妙应用。
在实际应用中,计数孤岛算法可以延伸至图像处理中的连通区域分析、社交网络中的社群发现、电子设计自动化中的电路连通性检查等多个领域。理解这个问题的解法不仅能帮助我们掌握基础算法思想,还能培养将实际问题抽象为计算模型的能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题定义与示例分析
2.1 标准问题描述
给定一个由'1'(陆地)和'0'(水域)组成的二维网格,计算网格中"孤岛"的数量。孤岛被定义为由相邻的'1'组成的区域,相邻指的是水平或垂直方向上的连接。斜对角线方向上的'1'不被视为相连。
示例输入:
code复制[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
示例输出:3
2.2 边界条件分析
在实际编码中,我们需要考虑以下边界条件:
- 空矩阵或0x0矩阵应返回0
- 全'0'矩阵应返回0
- 全'1'矩阵应返回1
- 单行或单列矩阵的特殊情况
- 极大矩阵的内存限制问题
提示:在面试场景中,主动讨论边界条件能展现你的思维全面性。实际工程中,这些边界case往往是bug的高发区。
3. 基础解法:DFS/BFS实现
3.1 深度优先搜索实现
DFS是解决计数孤岛问题最直观的方法。基本思路是遍历矩阵,当遇到'1'时启动DFS,将所有相连的'1'标记为已访问,同时增加孤岛计数。
python复制def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or c < 0 or r >= rows or c >= cols or grid[r][c] != '1':
return
grid[r][c] = '#' # 标记为已访问
dfs(r+1, c)
dfs(r-1, c)
dfs(r, c+1)
dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(r, c)
return count
时间复杂度:O(M×N),其中M和N分别是矩阵的行数和列数。每个元素最多被访问一次。
空间复杂度:O(M×N),最坏情况下整个矩阵都是陆地,递归栈的深度可达M×N。
3.2 广度优先搜索实现
BFS是另一种常见的解法,使用队列代替递归栈,适合处理大规模数据时避免栈溢出。
python复制from collections import deque
def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
queue = deque([(r, c)])
grid[r][c] = '#'
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] = '#'
queue.append((nx, ny))
return count
BFS的时间复杂度同样为O(M×N),空间复杂度在最坏情况下也是O(M×N),但实际中会比DFS的递归实现更节省内存。
4. 进阶解法:并查集实现
4.1 并查集原理
并查集(Union-Find)是一种树型的数据结构,用于处理不相交集合的合并与查询问题。它支持两种操作:
- Find:查找元素所属集合
- Union:合并两个集合
在计数孤岛问题中,我们可以将每个'1'视为一个独立集合,然后通过Union操作合并相邻的'1'。
4.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.rank = [0] * (rows * cols)
self.count = sum(grid[r][c] == '1' for r in range(rows) for c in range(cols))
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:
if self.rank[rootx] > self.rank[rooty]:
self.parent[rooty] = rootx
elif self.rank[rootx] < self.rank[rooty]:
self.parent[rootx] = rooty
else:
self.parent[rooty] = rootx
self.rank[rootx] += 1
self.count -= 1
def numIslands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
uf = UnionFind(grid)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
index = r * cols + c
if r > 0 and grid[r-1][c] == '1':
uf.union(index, (r-1)*cols + c)
if c > 0 and grid[r][c-1] == '1':
uf.union(index, r*cols + (c-1))
return uf.count
并查集的时间复杂度可以近似看作O(M×N×α(M×N)),其中α是反阿克曼函数,增长极其缓慢,可以认为是常数时间。
5. 性能对比与优化策略
5.1 三种方法对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| DFS | O(M×N) | O(M×N) | 代码简洁,适合小规模数据 |
| BFS | O(M×N) | O(min(M,N)) | 适合大规模数据,避免栈溢出 |
| 并查集 | O(M×N×α(M×N)) | O(M×N) | 需要动态处理连接关系时 |
5.2 优化技巧
- 原地修改技巧:直接修改输入矩阵来标记已访问元素,节省额外空间
- 方向数组简化:使用[(1,0),(-1,0),(0,1),(0,-1)]表示四个方向
- 提前终止条件:当剩余未访问的'1'数量为0时可以提前终止
- 并行处理:对于极大矩阵,可以考虑分块并行处理
注意:在实际面试中,面试官可能会要求你不能修改原矩阵。这时需要额外使用visited数组来记录访问状态。
6. 变种问题与扩展应用
6.1 常见变种问题
- 统计孤岛面积:找出最大的孤岛或所有孤岛的面积
- 孤岛周长计算:计算每个孤岛的周长
- 动态孤岛计数:支持动态添加/删除陆地后的实时计数
- 三维孤岛问题:扩展到三维空间的连通区域计数
- 彩色孤岛问题:不同颜色的陆地视为不同孤岛
6.2 实际应用场景
- 图像处理:连通区域分析,如OCR中的字符识别
- 社交网络:发现用户社群关系
- 游戏开发:地图区域划分与路径寻找
- 电路设计:检查电路连通性
- 医学影像:分析CT/MRI扫描结果中的组织区域
7. 常见错误与调试技巧
7.1 典型错误案例
- 无限递归:忘记标记已访问节点导致重复访问
- 边界检查遗漏:访问矩阵时未检查数组越界
- 方向定义错误:错误地包含了斜对角线方向
- 输入处理不当:未处理空输入或非法字符
- 变量混淆:在嵌套循环中使用相似的变量名
7.2 调试建议
- 可视化调试:打印每一步的矩阵状态
- 小规模测试:先用3x3或4x4的矩阵测试
- 边界测试:专门测试全0、全1、单行等特殊情况
- 逐步执行:在IDE中设置断点逐步跟踪执行
- 对比验证:用不同方法实现并交叉验证结果
我在实际编码中发现,最容易出错的地方是方向数组的定义和边界条件的检查。一个实用的技巧是预先定义好方向偏移量:
python复制directions = [(-1,0), (1,0), (0,-1), (0,1)] # 上下左右
然后在遍历时使用:
python复制for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
# 处理相邻单元格
这种方法既清晰又不容易出错,特别适合在压力环境下(如技术面试)快速编写正确代码。
