力扣实训 _ [200].岛屿数量

1.题目回顾

  • 输入: 一个由字符 '1'(陆地)和 '0'(水域)组成的 m x n 二维网格 grid

  • 输出: 计算网格中岛屿的数量。

  • 岛屿定义: 岛屿总是被水包围,并且只能由水平或垂直方向相邻的陆地连接形成。

  • 示例:
    输入:文本

    复制代码
    grid = [
      ["1","1","0","0","0"],
      ["1","1","0","0","0"],
      ["0","0","1","0","0"],
      ["0","0","0","1","1"]
    ]

    输出:3(左上角1个,中间1个,右下角1个)


2.核心思路:洪水填充(Flood Fill)

这道题的本质是求连通分量的数量 。最直观的解法就是使用深度优先搜索(DFS)广度优先搜索(BFS)

我们可以把这道题想象成"陆地沉没"的过程:

  1. 遍历网格: 我们挨个扫描网格中的每一个格子。
  2. 发现陆地: 当我们遇到一个 '1' 时,说明我们发现了一座新岛屿,此时岛屿计数器加 1。
  3. 洪水填充(沉没岛屿): 为了不重复计算这座岛屿的其他部分,我们需要立刻以当前格子为起点,通过 DFS 或 BFS 把它上下左右 所有相连的陆地 '1' 全部"淹没"(标记为 '0')。
  4. 重复: 继续扫描,直到整个网格都被遍历完。

3.算法详细步骤(以 DFS 为例)

初始化计数器

创建一个变量 count = 0,用来记录岛屿的数量。

双重循环遍历

使用两层 for 循环遍历 grid 中的每一个坐标 (i, j)

触发 DFS

如果当前坐标 grid[i][j] == '1'

  1. count 加 1。
  2. 调用 DFS 函数,传入当前坐标 (i, j)

DFS 递归逻辑

在 DFS 函数中:

  1. 边界检查: 如果坐标越界,或者当前格子已经是水域 '0',直接返回。
  2. 沉没当前陆地: 将当前格子 grid[i][j] 设为 '0'(标记为已访问)。
  3. 向四个方向扩散: 递归调用 DFS 处理上、下、左、右四个相邻的格子。

4. 代码实现 (Java & Kotlin)

Java 版本

复制代码
class Solution {
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) return 0;

        int count = 0;
        int rows = grid.length;
        int cols = grid[0].length;

        // 1. 双重循环遍历整个网格
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // 2. 发现陆地,岛屿数量+1,并触发DFS将其淹没
                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); // 右
    }
}

Kotlin版本

复制代码
class Solution {
    fun numIslands(grid: Array<CharArray>): Int {
        if (grid.isEmpty()) return 0

        var count = 0
        val rows = grid.size
        val cols = grid[0].size

        for (i in 0 until rows) {
            for (j in 0 until cols) {
                if (grid[i][j] == '1') {
                    count++
                    dfs(grid, i, j)
                }
            }
        }
        return count
    }

    private fun dfs(grid: Array<CharArray>, i: Int, j: Int) {
        val rows = grid.size
        val cols = grid[0].size

        // 边界检查
        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)
    }
}

复杂度分析

假设网格的行数为 M ,列数为 N。

时间复杂度: O(M×N)

在最坏的情况下(比如整个网格全是陆地),我们需要访问每一个格子一次。虽然 DFS 会递归,但每个格子最多只会被访问和修改一次(从 '1' 变成 '0'),因此总的时间复杂度是线性的。

空间复杂度: O(M×N)

空间复杂度主要取决于递归调用栈的深度。

  • 最坏情况: 网格全是陆地,DFS 会递归 M×N层,此时空间复杂度为 O(M×N)。
  • 最好情况: 网格非常平坦,递归深度较小。
相关推荐
To_OC2 分钟前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
鱼鱼不愚与5 小时前
《原来如此 | 第01期:为什么导航软件能预测红绿灯倒计时?》
算法
复杂网络9 小时前
论最小 Agent 计算机的形态
算法
kisshyshy1 天前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法