1. 计数孤岛问题解析
计数孤岛(Counting Islands)是计算机科学中一个经典的图论问题,也是算法面试中的高频考题。这个问题要求我们统计二维矩阵中由相邻的"1"(陆地)组成的独立区域数量,其中"0"表示水域。相邻指的是水平或垂直方向上的连接,对角线不算相邻。
我第一次遇到这个问题是在准备技术面试时,当时觉得它看起来很简单,但实际编码时才发现有很多细节需要考虑。经过多次练习和优化,我总结出了一套系统的解决思路,下面就来详细分享这个问题的解法及其变种。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础解法与思路分析
2.1 问题建模与输入输出
典型的输入是一个由'0'和'1'组成的二维矩阵,例如:
code复制[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
这个矩阵表示的地图中,有3个孤岛(独立的'1'区域)。我们的目标是编写一个函数,输入这样的矩阵,返回孤岛的数量。
2.2 深度优先搜索(DFS)解法
DFS是最直观的解决方法,思路如下:
- 遍历矩阵中的每个单元格
- 当遇到'1'时,增加计数器并启动DFS
- 在DFS过程中,将所有相连的'1'标记为已访问(可以改为'0')
- 继续遍历,跳过已访问的单元格
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] = '0' # 标记为已访问
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
注意:这里直接修改了输入矩阵来标记访问状态,如果不允许修改原矩阵,需要额外使用一个visited矩阵来记录访问状态。
2.3 广度优先搜索(BFS)解法
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] = '0'
while queue:
row, col = queue.popleft()
for dr, dc in [(1,0), (-1,0), (0,1), (0,-1)]:
nr, nc = row + dr, col + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
queue.append((nr, nc))
grid[nr][nc] = '0'
return count
3. 算法优化与进阶解法
3.1 并查集(Union-Find)解法
并查集是解决连通性问题的经典数据结构,特别适合处理动态连通性问题。对于孤岛计数,我们可以:
- 初始化:将每个'1'视为独立的集合
- 遍历矩阵,将相邻的'1'合并
- 最后统计剩余的独立集合数量
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 r in range(rows):
for c in range(cols):
if grid[r][c] == '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 r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
grid[r][c] = '0'
for dr, dc in [(1,0), (0,1)]:
nr, nc = r + dr, c + dc
if nr < rows and nc < cols and grid[nr][nc] == '1':
uf.union(r * cols + c, nr * cols + nc)
return uf.count
3.2 性能比较与适用场景
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| DFS | O(M×N) | O(M×N) | 简单实现,递归深度可能成为问题 |
| BFS | O(M×N) | O(min(M,N)) | 适合大规模数据,避免递归栈溢出 |
| 并查集 | O(M×N×α(M×N)) | O(M×N) | 适合动态更新场景,实现较复杂 |
其中α是反阿克曼函数,增长极其缓慢,可以认为是常数时间。
4. 常见问题与调试技巧
4.1 边界条件处理
在实际编码中,有几个常见的边界条件需要注意:
- 空输入处理:函数应该能处理空矩阵或None输入
- 矩阵维度:确保行和列的索引不越界
- 全'0'或全'1'的情况:测试极端情况
4.2 访问标记策略
标记已访问单元格有三种常见方法:
- 直接修改原矩阵(最简单,但可能不允许)
- 使用额外的visited矩阵(空间开销)
- 使用哈希集合记录访问过的坐标(适用于稀疏矩阵)
4.3 内存优化技巧
对于非常大的矩阵,可以考虑以下优化:
- 使用BFS而非DFS,避免递归栈溢出
- 按行或列分块处理
- 使用位运算压缩存储空间
5. 问题变种与实际应用
5.1 孤岛最大面积
不仅要计数,还要找出最大的孤岛面积:
python复制def maxAreaOfIsland(grid):
max_area = 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 0
grid[r][c] = 0
return 1 + 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:
max_area = max(max_area, dfs(r, c))
return max_area
5.2 封闭孤岛计数
只统计完全被'0'包围的孤岛(边缘的孤岛不算):
python复制def closedIsland(grid):
rows, cols = len(grid), len(grid[0])
count = 0
def dfs(r, c):
if r < 0 or c < 0 or r >= rows or c >= cols:
return False
if grid[r][c] != 0:
return True
grid[r][c] = 1
res = True
for dr, dc in [(1,0), (-1,0), (0,1), (0,-1)]:
res = dfs(r+dr, c+dc) and res
return res
for r in range(rows):
for c in range(cols):
if grid[r][c] == 0 and dfs(r, c):
count += 1
return count
5.3 实际应用场景
- 图像处理:识别连通区域
- 游戏开发:地图区域划分
- 社交网络:发现独立社群
- 电路设计:识别短路区域
6. 面试技巧与注意事项
在技术面试中遇到孤岛计数问题时,建议采取以下策略:
- 先明确问题要求:确认相邻的定义、输入输出格式等
- 从暴力解法开始,逐步优化
- 讨论时间/空间复杂度
- 考虑边界条件和异常输入
- 如果时间允许,讨论并实现优化方案
常见面试问题:
- 如何处理非常大的矩阵?
- 如果矩阵是动态变化的怎么办?
- 如何并行化这个算法?
我在实际面试中遇到过几次这个问题,发现面试官最看重的是:
- 清晰的解题思路
- 代码实现的正确性
- 对算法复杂度的理解
- 处理边界条件的能力
建议在练习时,不仅要写出正确的代码,还要能解释每一步的思考过程,这样才能在面试中游刃有余。
