200. 岛屿数量
题解github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions
📌 题目描述
给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
✅ 示例说明
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
💡 解题思路
本题本质上是求 连通分量的数量 ,其中每个连通分量由相邻的 '1' 构成。我们可以使用以下两种主流方法:
方法一:深度优先搜索(DFS)
- 遍历整个二维网格。
- 当遇到
'1'时,说明发现了一个新岛屿,岛屿数量 +1。 - 使用 DFS 将该岛屿所有相连的
'1'标记为'0'(即"淹没"该岛),防止重复计数。 - 继续遍历直到结束。
✅ 优点 :代码简洁,逻辑清晰。
❌ 缺点:递归可能导致栈溢出(但在 LeetCode 数据规模下通常不会)。
方法二:广度优先搜索(BFS)
- 遍历网格。
- 遇到
'1'时,岛屿数 +1,并启动 BFS。 - 使用队列将当前岛屿所有相连的
'1'入队,并在访问后置为'0'。 - 直到队列为空,表示该岛屿已完全遍历。
✅ 优点 :避免递归,适合大规模数据。
❌ 缺点:需要额外队列空间。
方法三:并查集(Union-Find)
- 将每个
'1'视为一个独立节点。 - 遍历网格,对每个
'1',检查其右方和下方是否也是'1',若是,则合并。 - 最终统计并查集中连通分量的数量。
✅ 优点 :适合动态连通性问题。
❌ 缺点:实现较复杂,本题中不如 DFS/BFS 直观高效。
📌 推荐解法:DFS 或 BFS。本题采用 DFS 实现更简洁。
💻 代码实现
java
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
int m = grid.length, n = grid[0].length;
int count = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
count++;
dfs(grid, i, j);
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
// 边界检查或已访问(水)
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {
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);
}
}
go
func numIslands(grid [][]byte) int {
if len(grid) == 0 || len(grid[0]) == 0 {
return 0
}
m, n := len(grid), len(grid[0])
count := 0
var dfs func(i, j int)
dfs = func(i, j int) {
if i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0' {
return
}
grid[i][j] = '0'
dfs(i+1, j)
dfs(i-1, j)
dfs(i, j+1)
dfs(i, j-1)
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == '1' {
count++
dfs(i, j)
}
}
}
return count
}
🔍 示例演示
以示例 2 为例:
初始网格:
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
- 在 (0,0) 发现
'1'→ 岛屿1,DFS 淹没左上角 2x2 区域。 - 在 (2,2) 发现
'1'→ 岛屿2,DFS 淹没单点。 - 在 (3,3) 发现
'1'→ 岛屿3,DFS 淹没右下角两个点。
最终返回 3 ✅
✅ 答案有效性证明
- 正确性 :每次遇到未访问的
'1'必定属于一个新岛屿(因为之前的 DFS/BFS 已清除所有已访问岛屿)。 - 完整性:遍历所有格子,确保不遗漏任何岛屿。
- 终止性:DFS/BFS 在有限网格中必然终止(每次递归/入队都缩小问题规模)。
因此,算法能正确统计所有连通的 '1' 区域数量。
📊 复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| DFS | O ( M t i m e s N ) O(M \\times N) O(MtimesN) | O ( M t i m e s N ) O(M \\times N) O(MtimesN) | 最坏情况递归深度为 M t i m e s N M \\times N MtimesN(全为1) |
| BFS | O ( M t i m e s N ) O(M \\times N) O(MtimesN) | O ( m i n ( M , N ) ) O(\\min(M, N)) O(min(M,N)) | 队列最大存储为对角线长度 |
| 并查集 | O ( M t i m e s N c d o t a l p h a ( M N ) ) O(M \\times N \\cdot \\alpha(MN)) O(MtimesNcdotalpha(MN)) | O ( M t i m e s N ) O(M \\times N) O(MtimesN) | a l p h a \\alpha alpha 为阿克曼反函数,≈常数 |
其中 M M M 为行数, N N N 为列数。
🧠 问题总结
- ❌ 常见错误 :忘记将访问过的
'1'置为'0',导致重复计数。 - ✅ 关键思想 :连通分量计数 + 图遍历(DFS/BFS)。
- 💡 扩展思考 :
- 若岛屿可斜向连接?→ 修改 DFS 方向为 8 个。
- 若需返回最大岛屿面积?→ 在 DFS 中累加面积。
- 动态添加陆地?→ 考虑并查集。
本题是 图论基础题,掌握后可轻松应对类似"区域染色"、"连通块统计"等问题。