文章目录
前言
本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。
一、题目
1、原题链接
2、题目描述
二、个人思路整理
1、思路分析
这道题本质是求解二维网格中的连通分量个数。
核心步骤:
- 主循环遍历网格:逐行逐列扫描网格中的每个单元格。
- 触发搜索(发现新岛屿):当遇到字符
'1'(陆地)时,说明发现了一座新岛屿,将答案计数器加 1 1 1。 - 沉岛/标记访问(避免重复计数):以该点为起点进行深度优先搜索 (DFS) 或 广度优先搜索 (BFS),将所有与该陆地上下左右相连的
'1'全部置为'0'(或标记为已访问'2')。 - 结束条件:整张地图遍历完成后,计数器的值即为岛屿总数。
2、解题代码
cpp
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
// 获取网格的行数和列数
int m = grid.size();
int n = grid[0].size();
int islands = 0; // 记录岛屿总数
// 遍历网格中的每一个单元格
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 当发现陆地 '1' 时,说明发现了一座新岛屿
if (grid[i][j] == '1') {
islands++;
// 通过 DFS 将当前陆地连通的所有陆地全部"淹没"(置为 '0'),避免重复统计
dfs(grid, i, j, m, n);
}
}
}
return islands;
}
private:
void dfs(vector<vector<char>>& grid, int r, int c, int m, int n) {
// 递归终止条件:越界 或 当前位置不是陆地(是水 '0' 或已访问过)
if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] != '1') {
return;
}
// 沉岛标记:将当前陆地修改为 '0',表示已访问
grid[r][c] = '0';
// 递归向上下左右四个方向扩展搜索
dfs(grid, r - 1, c, m, n); // 上
dfs(grid, r + 1, c, m, n); // 下
dfs(grid, r, c - 1, m, n); // 左
dfs(grid, r, c + 1, m, n); // 右
}
};
复杂度分析
- 时间复杂度: O ( M × N ) O(M \times N) O(M×N),每个单元格最多被访问常数次。
- 空间复杂度: O ( M × N ) O(M \times N) O(M×N),最坏情况下(整张图都是陆地),DFS 递归调用栈的深度达到 M × N M \times N M×N。
三、知识风暴
深度优先搜索(DFS)与广度优先搜索(BFS) 是本题的两大核心解法。岛屿数量问题本质上是在二维网格上寻找连通分量,而 DFS 与 BFS 正是遍历连通分量的两种经典策略,理解二者的差异对掌握本题至关重要。
算法核心思想:
- DFS(沉岛法) :遇到陆地
'1'时,沿上下左右四个方向递归深入,把相连的陆地全部置为'0'(沉岛),从而标记整座岛屿已访问。递归天然契合"一路走到底再回头"的探索方式。 - BFS(扩散法) :遇到陆地
'1'时,借助队列逐层向外扩散,把同一岛屿的陆地依次入队并置为'0',按"由近及远"的顺序完成整座岛屿的标记。 - 共同点:两种方法都遵循"发现新陆地即计数 + 标记整座岛屿"的框架,区别仅在于遍历顺序与辅助数据结构(递归栈 vs 显式队列)。
常见对比:DFS vs BFS
- DFS(递归实现) :代码简洁直观,利用系统调用栈保存回溯路径;但最坏情况下(整张图都是陆地)递归深度可达 O ( M × N ) O(M \times N) O(M×N),存在栈溢出风险。
- DFS(显式栈实现):用栈模拟递归过程,避免系统栈溢出,但需要手动维护入栈出栈顺序,代码相对繁琐。
- BFS(队列实现) :按层扩散,天然避免递归深度问题,空间复杂度在最坏情况下为 O ( min ( M , N ) ) O(\min(M, N)) O(min(M,N))(队列中最多同时存在一层的节点),更适合网格较大的场景。
"沉岛"标记思想:
- 核心思想 :遍历到陆地后立即将其置为
'0',相当于"淹没"该陆地,避免后续遍历重复计数。 - 与本题的联系 :每发现一个
'1'就代表一座新岛屿,通过沉岛把整座岛屿从地图上"抹去",从而保证每个岛屿只被计数一次。 - 注意事项 :也可以使用独立的
visited二维数组标记访问状态,但会额外占用 O ( M × N ) O(M \times N) O(M×N) 空间;原地修改grid更省空间,但会改变输入数据,需根据题目要求权衡。
使用要点:
- 方向数组 :用
int dirs[4][2] = {``{-1,0},{1,0},{0,-1},{0,1}}统一表示上下左右四个方向,配合循环遍历,可避免重复书写四行递归调用。 - 边界检查 :递归或入队前务必判断行列下标是否越界,以及当前字符是否为
'1',这是防止无限递归和重复访问的关键。 - 计数时机 :计数器
islands++必须放在"发现新陆地"的瞬间,即触发 DFS/BFS 之前,而不是在遍历过程中累加。 - 主循环与搜索分离:主循环只负责扫描网格并触发搜索,具体的连通分量标记逻辑封装在独立的 DFS/BFS 函数中,保持代码结构清晰。
算法变体与扩展:
- 岛屿的最大面积(LeetCode 695):在 DFS/BFS 遍历时同步统计每座岛屿的面积,取最大值即可,是本题最直接的变体。
- 被围绕的区域(LeetCode 130) :从边界出发标记不被包围的
'O',再翻转其余'O',是"从边界反向搜索"的经典应用。 - 太平洋大西洋水流问题(LeetCode 417):从边界反向 DFS,标记能到达两个海洋的格子,进一步体会"逆向搜索"思想。
- 腐烂的橘子(LeetCode 994):多源 BFS 的典型例题,用队列同时从多个腐烂橘子出发扩散,可加深对 BFS 分层遍历的理解。
相关 LeetCode 例题:
- 695. 岛屿的最大面积(DFS/BFS 统计连通块大小)
- 130. 被围绕的区域(边界反向搜索)
- 417. 太平洋大西洋水流问题(多源逆向 DFS)
- 994. 腐烂的橘子(多源 BFS 分层扩散)

