文章目录
一、题目简介
题目链接:
LeetCode - Number of Islands
题目描述:
给你一个 m × n 的二维网格 grid,由字符 '1'(陆地)和 '0'(水域)组成。请你计算网格中 岛屿 的数量。岛屿被水包围,可以由水平或垂直方向相邻的陆地连接形成。
示例输入:
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
示例输出:
3
解释:上图中存在三个岛屿。
二、问题本质
岛屿数量问题 的核心是:
将矩阵看作一个图,"1" 表示陆地,"0" 表示水域。岛屿是一个连通分量(Connected Component)。
我们需要计算:
- 以
'1'为节点; - 上下左右方向为边;
- 统计这个"图"中连通分量的数量。
三、解法总体思维导图
岛屿数量
DFS
标记访问过的节点
递归遍历相邻陆地
时间复杂度 O(m*n)
空间复杂度 O(m*n)
BFS
使用队列保存待访问节点
层层扩展陆地
时间复杂度 O(m*n)
空间复杂度 O(min(m,n))
Union-Find
每个格子设为一个节点
通过合并操作连接陆地
最终统计父节点数量
时间复杂度 O(mnα(m*n))
空间复杂度 O(m*n)
四、方法一:深度优先搜索(DFS)
解题思路
- 遍历整个网格;
- 每当遇到陆地
'1',计数器 +1; - 调用 DFS,将该陆地及所有相连的陆地标记为
'0'(即访问过); - 遍历结束后,计数值即为岛屿数量。
DFS 过程流程图
是
否
开始遍历grid
当前格子是1吗?
岛屿数量+1
调用DFS递归标记相邻陆地
继续遍历下一个格子
遍历结束返回计数
Java代码实现
java
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
int rows = grid.length;
if (rows == 0) return 0;
int cols = grid[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
count++;
dfs(grid, i, j);
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
int rows = grid.length;
int cols = grid[0].length;
if (i < 0 || i >= rows || j < 0 || j >= cols || 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);
}
}
复杂度分析
| 类别 | 复杂度 |
|---|---|
| 时间复杂度 | O(m × n) --- 每个格子只会被访问一次 |
| 空间复杂度 | O(m × n) --- 递归栈深度最大为网格大小 |
五、方法二:广度优先搜索(BFS)
解题思路
- 与 DFS 类似,只是使用队列代替栈;
- 每当发现新的陆地,将其入队;
- 不断扩展相邻节点,完成一整个岛屿的遍历。
BFS 时序图
Algorithm Queue Grid Algorithm Queue Grid loop [每个队列元素] 遍历发现'1' 加入队列 取出当前节点 标记为'0' 加入相邻陆地 完成一个岛屿扩展
Java代码实现
java
import java.util.*;
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
int rows = grid.length;
if (rows == 0) return 0;
int cols = grid[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
count++;
bfs(grid, i, j);
}
}
}
return count;
}
private void bfs(char[][] grid, int x, int y) {
int rows = grid.length;
int cols = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{x, y});
grid[x][y] = '0';
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
while (!queue.isEmpty()) {
int[] cur = queue.poll();
for (int[] d : dirs) {
int nx = cur[0] + d[0];
int ny = cur[1] + d[1];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && grid[nx][ny] == '1') {
grid[nx][ny] = '0';
queue.offer(new int[]{nx, ny});
}
}
}
}
}
复杂度分析
| 类别 | 复杂度 |
|---|---|
| 时间复杂度 | O(m × n) |
| 空间复杂度 | O(min(m, n)) --- 队列最大长度 |
六、方法三:并查集(Union-Find)
解题原理
- 将每个陆地格子看作一个节点;
- 若相邻两格都是
'1',则进行 "合并" 操作; - 最后统计有多少个不同的集合(根节点数量),即岛屿数量。
并查集流程图
否
是
初始化并查集结构
遍历grid
当前格子为1?
跳过
检查上下左右相邻节点
合并相邻陆地节点
更新集合数量
返回岛屿数量
Java代码实现
java
class Solution {
public int numIslands(char[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
UnionFind uf = new UnionFind(grid);
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
for (int[] d : dirs) {
int nx = i + d[0];
int ny = j + d[1];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && grid[nx][ny] == '1') {
uf.union(i * cols + j, nx * cols + ny);
}
}
}
}
}
return uf.getCount();
}
}
class UnionFind {
int[] parent;
int count;
int rows, cols;
public UnionFind(char[][] grid) {
rows = grid.length;
cols = grid[0].length;
parent = new int[rows * cols];
count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
int id = i * cols + j;
parent[id] = id;
count++;
}
}
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
count--;
}
}
public int getCount() {
return count;
}
}
复杂度分析
| 类别 | 复杂度 |
|---|---|
| 时间复杂度 | O(m × n × α(m × n)) (α为Ackermann函数,极小趋近常数) |
| 空间复杂度 | O(m × n) |
七、总结对比
| 解法 | 思路类型 | 时间复杂度 | 空间复杂度 | 优缺点 |
|---|---|---|---|---|
| DFS | 递归搜索 | O(m×n) | O(m×n) | 实现简单,逻辑直观;递归栈较深时占内存较大 |
| BFS | 队列搜索 | O(m×n) | O(min(m,n)) | 可避免递归栈溢出;代码稍繁琐 |
| Union-Find | 数据结构合并 | O(m×n×α) | O(m×n) | 理论优雅;代码复杂;适合更大规模场景 |