【图论】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 例题

相关推荐
木井巳6 小时前
【记忆化搜索】斐波那契数
java·算法·leetcode·深度优先·剪枝·推荐算法
土司大王1 天前
LeetCode 79 单词搜索:Java 回溯模板、网格 DFS 与剪枝优化
java·算法·leetcode·深度优先
Lyyaoo.1 天前
【图论】岛屿数量/腐烂的橘子/课程表/实现前缀树
图论
lvwangshu3 天前
图论:LCA、树的直径、树的重心、二分图与 Tarjan 缩点
算法·图论
木井巳4 天前
【BFS/DFS 解决 FloodFill 算法】衣橱整理
java·算法·leetcode·深度优先·广度优先·宽度优先
Nil2084 天前
leetcode 22括号生成
算法·leetcode·深度优先
hansang_IR5 天前
【题解】[APIO2023] 赛博乐园 / cyberland
c++·算法·图论
哪 吒5 天前
华为OD机试 - 云服务安全策略最优选择 - 深度优先搜索DFS(Java 新系统 200分)
java·华为od·深度优先
aqiu1111115 天前
【并查集 / 图论】蓝桥云课 - 魔法大陆的城市群(求无向图连通块数量)题解
蓝桥杯·图论