岛屿数量(Number of Islands)详细解题笔记
1. 题目描述
题目名称:岛屿数量
题目描述:给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量。岛屿被水包围,并且通过水平或垂直方向上相邻的陆地连接而成。
输入:
`grid = [
['1','1','1','1','0'],
['1','1','0','1','0'],
['1','1','0','0','0'],
['0','0','0','0','0']
]`
输出:1
解释:所有陆地都相连,形成一个岛屿。
约束条件:
m == grid.lengthn == grid[i].length1 <= m, n <= 300grid[i][j]的值为 '0' 或 '1'
2. 解题思路分析
方法一:深度优先搜索(DFS)推荐
思路:遍历每个网格点,当发现陆地('1')时,使用DFS标记所有相连的陆地为已访问(沉没岛屿),岛屿计数+1。
时间复杂度:O(m × n) - 每个网格点最多访问一次
空间复杂度:O(m × n) - 最坏情况下递归深度
#include<iostream>
#include<vector>
using namespace std;
void dfs(vector<vector<char>>& graph,int r,int c){//当前行,当前列
int nr=graph.size();
int nc=graph[0].size();
graph[r][c]='0';//标记已经访问
//检查上下左右
if(r>0&&graph[r-1][c]=='1'){
dfs(graph,r-1,c);
}
if(r<nr-1&&graph[r+1][c]=='1'){
dfs(graph,r+1,c);
}
if(c>0&&graph[r][c-1]=='1'){
dfs(graph,r,c-1);
}
if(c<nc-1&&graph[r][c+1]=='1'){
dfs(graph,r,c+1);
}
}
int main(){
vector<vector<char>>graph={{'1','1','1','1','0'},{'1','1','0','1','0'},{'1','1','0','0','0'},{'0','0','0','0','0'}};
int result=0;
for(int i=0;i<graph.size();i++){
for(int j=0;j<graph[0].size();j++){
if(graph[i][j]=='1'){
dfs(graph,i,j);
result++;
}
}
}
cout<<result<<endl;
}
方法二:广度优先搜索(BFS)
思路:使用队列代替递归,实现层序遍历。
时间复杂度:O(m × n)
空间复杂度:O(min(m, n)) - 队列最大长度
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
void bfs(vector<vector<char>>& graph,int r,int c){//当前行,当前列
int nr=graph.size();
int nc=graph[0].size();
queue<pair<int,int>>q;//队列存储当前访问的节点
q.push({r,c});//将当前节点入队
graph[r][c]='0';//标记已经访问
while(!q.empty()){
auto node=q.front();//取出队头节点
q.pop();//将队头节点出队
int row=node.first;//当前行
int col=node.second;//当前列
//检查上下左右
if(row>0&&graph[row-1][col]=='1'){
q.push({row-1,col});
graph[row-1][col]='0';
}
if(row<nr-1&&graph[row+1][col]=='1'){
q.push({row+1,col});
graph[row+1][col]='0';
}
if(col>0&&graph[row][col-1]=='1'){
q.push({row,col-1});
graph[row][col-1]='0';
}
if(col<nc-1&&graph[row][col+1]=='1'){
q.push({row,col+1});
graph[row][col+1]='0';
}
}
}
int main(){
vector<vector<char>>graph={{'1','1','1','1','0'},{'1','1','0','1','0'},{'1','1','0','0','0'},{'0','0','0','0','0'}};
int result=0;
for(int i=0;i<graph.size();i++){
for(int j=0;j<graph[0].size();j++){
if(graph[i][j]=='1'){
bfs(graph,i,j);
result++;
}
}
}
cout<<result<<endl;
}
3. 核心思想与关键问题解答
Q1: 为什么选择DFS/BFS?
A: 岛屿问题本质是连通分量问题,DFS/BFS是解决连通性问题的标准方法,能高效标记所有相连节点。
Q2: 为什么使用原地修改(沉没岛屿)?
A: 将访问过的陆地标记为'0',既避免了重复访问,又节省了O(m×n)的额外空间,是空间最优解。
Q3: 该算法的适用场景是什么?
A: 适用于所有基于网格的连通性问题,如"被围绕的区域"、"最大岛屿面积"、"岛屿周长"等。
Q4: 如何扩展到同类问题?
A: 对于"统计岛屿最大面积",在DFS中增加计数器;对于"岛屿周长",统计陆地与水的边界。
4. 算法流程详解
以输入示例为例:
| 步骤 | 当前坐标 | 当前值 | 操作 | 岛屿计数 | 说明 |
|---|---|---|---|---|---|
| 1 | (0,0) | '1' | 发现新岛屿,DFS沉没 | 1 | 开始DFS |
| 2 | (0,1) | '1' | 标记'0',继续DFS | 1 | 向右扩散 |
| 3 | (0,2) | '1' | 标记'0',继续DFS | 1 | 向右扩散 |
| 4 | (0,3) | '1' | 标记'0',继续DFS | 1 | 向右扩散 |
| 5 | (0,4) | '0' | 停止向右 | 1 | 遇到水 |
| 6 | (1,0) | '1' | 标记'0',向下扩散 | 1 | 向下扩散 |
| ... | ... | ... | ... | 1 | 继续沉没所有相连陆地 |
| 结束 | - | - | - | 1 | 所有陆地已处理 |
最终输出:1
5. 总结 / 适用场景扩展
核心技巧:
- 使用DFS/BFS标记连通分量
- 原地修改节省空间复杂度
- 边界检查防止数组越界
同类题型扩展:
- 最大岛屿面积:在DFS中统计连通块大小
- 被围绕的区域:从边界开始DFS标记不被包围的区域
- 岛屿周长:统计陆地与水的相邻边数
- 统计封闭岛屿:排除边界连接的岛屿
6. 边界情况 / 测试用例 / 变体思考
边界测试用例:
- 全水网格:
[['0','0'],['0','0']]→ 返回0 - 全陆网格:
[['1','1'],['1','1']]→ 返回1 - 单点岛屿:
[['1','0'],['0','0']]→ 返回1 - 无连接岛屿:
[['1','0'],['0','1']]→ 返回2