1. 岛屿数量问题解析
这道题目来自力扣热门题库Hot100系列,编号200题。题目要求我们计算一个由'1'(陆地)和'0'(水)组成的二维网格中岛屿的数量。岛屿被定义为由相邻的'1'组成的区域,相邻指的是水平或垂直方向上相连的'1'。
1.1 问题示例
考虑以下5x5的网格示例:
code复制11110
11010
11000
00000
这个网格中有1个岛屿,因为所有'1'都是相连的。
再看另一个例子:
code复制11000
11000
00100
00011
这个网格中有3个岛屿:左上角的4个'1'组成一个岛屿,中间的单个'1'是一个岛屿,右下角的2个'1'组成第三个岛屿。
2. 解题思路分析
2.1 深度优先搜索(DFS)解法
DFS是最直观的解决方法。基本思路是:
- 遍历整个网格
- 当遇到'1'时,岛屿计数加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)
2.2 广度优先搜索(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 i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
queue = deque([(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
3. 算法复杂度分析
3.1 时间复杂度
两种方法的时间复杂度都是O(M×N),其中M是行数,N是列数。因为我们需要访问网格中的每个元素至少一次。
3.2 空间复杂度
DFS的空间复杂度最坏情况下是O(M×N),当整个网格都是陆地时,递归栈的深度可能达到M×N。
BFS的空间复杂度是O(min(M,N)),因为队列中最多同时存储网格对角线上的元素数量。
4. 优化与变种问题
4.1 并查集(Union-Find)解法
并查集是解决连通性问题的经典数据结构:
python复制class UnionFind:
def __init__(self, grid):
rows, cols = len(grid), len(grid[0])
self.count = 0
self.parent = [i for i in range(rows * cols)]
self.rank = [0] * (rows * cols)
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:
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 i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
grid[i][j] = '0'
for x, y in [(i-1,j), (i+1,j), (i,j-1), (i,j+1)]:
if 0 <= x < rows and 0 <= y < cols and grid[x][y] == '1':
uf.union(i * cols + j, x * cols + y)
return uf.count
4.2 变种问题
- 岛屿的最大面积:找出网格中最大的岛屿面积
- 封闭岛屿数量:统计完全被水包围的岛屿数量
- 不同岛屿的数量:考虑岛屿形状不同才算不同岛屿
5. 实际应用场景
岛屿数量问题在实际中有多种应用:
- 图像处理中的连通区域分析
- 社交网络中的群体检测
- 地图分析中的地块划分
- 电路板中的连通性检查
6. 常见错误与调试技巧
6.1 常见错误
- 忘记处理空输入的情况
- 数组越界访问
- 没有正确标记已访问的节点
- 在BFS实现中错误处理队列
6.2 调试技巧
- 打印中间状态:在每次发现新岛屿时打印网格状态
- 使用小测试用例:先用3x3或2x2的网格测试
- 边界测试:全'1'网格、全'0'网格、单行/单列网格
7. 性能优化建议
- 对于特别大的网格,可以考虑并行处理
- 在实际应用中,可以预处理网格数据
- 对于频繁查询的场景,可以使用并查集并缓存结果
8. 不同语言的实现差异
8.1 Java实现
java复制public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int count = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
count++;
dfs(grid, i, j);
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != '1') {
return;
}
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
8.2 C++实现
cpp复制class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
if (grid.empty()) return 0;
int count = 0;
for (int i = 0; i < grid.size(); ++i) {
for (int j = 0; j < grid[0].size(); ++j) {
if (grid[i][j] == '1') {
++count;
dfs(grid, i, j);
}
}
}
return count;
}
void dfs(vector<vector<char>>& grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.size() || j >= grid[0].size() || grid[i][j] != '1') {
return;
}
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
};
9. 面试技巧
在面试中遇到这类问题时:
- 先明确问题要求,确认边界条件
- 解释你的思路,包括时间和空间复杂度
- 先给出暴力解法,再考虑优化
- 讨论可能的变种问题
- 注意代码风格和变量命名
10. 学习资源推荐
- 《算法导论》中的图算法章节
- 力扣上的相关题目:
- 岛屿的最大面积(695)
- 封闭岛屿数量(1254)
- 不同岛屿的数量(694)
- 可视化算法学习网站:VisuAlgo
在实际编码练习中,我发现从简单的DFS/BFS实现开始,再逐步优化到并查集解法,能够更好地理解问题的本质。对于大规模数据,并查集通常表现更好,但实现起来也更为复杂。根据具体场景选择合适的算法很重要。
