【图论】LC 200.岛屿数量

文章目录

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

200.岛屿数量

2、题目描述

二、个人思路整理

1、思路分析

这道题本质是求解二维网格中的连通分量个数。

核心步骤:

  1. 主循环遍历网格:逐行逐列扫描网格中的每个单元格。
  2. 触发搜索(发现新岛屿):当遇到字符 '1'(陆地)时,说明发现了一座新岛屿,将答案计数器加 1 1 1。
  3. 沉岛/标记访问(避免重复计数):以该点为起点进行深度优先搜索 (DFS) 或 广度优先搜索 (BFS),将所有与该陆地上下左右相连的 '1' 全部置为 '0'(或标记为已访问 '2')。
  4. 结束条件:整张地图遍历完成后,计数器的值即为岛屿总数。

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 例题:

相关推荐
春弦_5 小时前
自用算法整理二
数据结构·算法·图论
NEFU_Jyj18 小时前
牛客周赛R166(max型瓶颈最短路,前后缀DP合并)
算法·深度优先
lvwangshu1 天前
P8981 「DROI」Round 1 距离 题解
动态规划·图论·题解·dp·树形dp·技巧·换根dp·性质题·思维好题
海海不掉头发2 天前
图论与应用_课堂笔记
笔记·图论
云淡风轻~窗明几净2 天前
角谷猜想最接近严格证明的测试 Version1.1.1
人工智能·算法·机器学习·图论
云淡风轻~窗明几净3 天前
角谷猜想与百度AI的对话dialog(原始稿)
人工智能·算法·机器学习·图论
?abc!3 天前
(动态规划+DFS)494. 目标和
算法·深度优先·动态规划
罗湖老棍子3 天前
【例 1】Sightseeing Trip(信息学奥赛一本通- P1494)(洛谷-P10927)
数据结构·算法·图论·最短路·floyd·最小环
罗湖老棍子4 天前
【例题1】黑暗城堡(信息学奥赛一本通- P1486)
算法·图论·最短路·dijkstra·最小生成树·最短路径生成树
INGNIGHT5 天前
815 · 课程表IV(dfs)
算法·深度优先