当前位置: 首页 > news >正文

【LeetCode 热题 100】200. 岛屿数量——DFS

Problem: 200. 岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(M * N)
    • 空间复杂度:O(M * N) (最坏情况)

整体思路

这段代码旨在解决一个经典的二维网格问题:岛屿数量 (Number of Islands)。问题要求计算一个由 ‘1’(陆地)和 ‘0’(水)组成的二维网格中,岛屿的总数。岛屿被定义为水平或垂直方向上相邻的 ‘1’ 连接而成的区域。

该算法采用了一种非常直观且高效的 深度优先搜索 (DFS) 策略,其核心思想是“沉岛”或“淹没”法。

  1. 遍历网格寻找新岛屿

    • 算法使用两层嵌套的 for 循环来逐一扫描网格中的每一个单元格 (i, j)
    • 当扫描到一个值为 '1' 的单元格时,这标志着我们发现了一个新岛屿(或者是一个已知岛屿的未被访问部分)。
  2. 发现新岛屿后的处理

    • 一旦发现一个 '1',就将岛屿计数器 ans 加一。
    • 紧接着,以这个发现的 '1' 单元格 (i, j) 为起点,立即调用一个 dfs 辅助函数。
  3. DFS 沉岛/淹没过程

    • dfs 函数的任务是:从给定的起点 (i, j) 开始,通过递归的方式,找到所有与它相连的(水平或垂直)‘1’ 单元格,并将它们全部标记为已访问。
    • 标记方法:代码中通过将 '1' 修改为 '2' 来实现标记。这是一种原地修改的技巧,避免了使用额外的 visited 数组。任何非 '1' 的值(如 ‘0’ 或 ‘2’)都表示水域或已访问过的陆地。
    • 递归探索:在标记完当前单元格后,dfs 函数会向其上下左右四个方向进行递归调用,继续探索和淹没相邻的陆地。
    • 边界与终止条件dfs 的递归有明确的终止条件:
      • 越出网格边界(ij 超出范围)。
      • 遇到水域或已访问过的陆地(grid[i][j] != '1')。
      • 当满足这些条件时,递归会停止,并返回上一层。
  4. 算法的正确性保证

    • 由于每次发现 '1' 后,整个与之相连的岛屿都会被 dfs 一次性地“淹没”(标记为 ‘2’),因此,主循环在后续的扫描中不会再次将这个岛屿的任何部分计为新岛屿。
    • 这就保证了每个岛屿只会被计数一次,从而得到正确的总数。

完整代码

class Solution {/*** 主函数:计算二维网格中岛屿的数量。* @param grid 一个由 '1' (陆地) 和 '0' (水) 组成的二维字符数组。* @return 岛屿的总数。*/public int numIslands(char[][] grid) {// ans: 用于累计岛屿的数量。int ans = 0;// 遍历网格的每一行for (int i = 0; i < grid.length; i++) {// 遍历网格的每一列for (int j = 0; j < grid[0].length; j++) {// 如果发现一个单元格是 '1' (未被访问过的陆地)if (grid[i][j] == '1') {// 这意味着我们发现了一个新的岛屿,计数器加 1。ans++;// 以此为起点,进行深度优先搜索,将整个岛屿“淹没”。dfs(grid, i, j);}}}// 返回最终的岛屿总数。return ans;}/*** 辅助函数:通过深度优先搜索(DFS)淹没与 (i, j) 相连的整个岛屿。* @param grid 网格* @param i 当前单元格的行索引* @param j 当前单元格的列索引*/private void dfs(char[][] grid, int i, int j) {// 递归的终止条件(边界检查):// 1. 行索引越界 (i < 0 或 i >= grid.length)// 2. 列索引越界 (j < 0 或 j >= grid[0].length)// 3. 当前单元格不是未访问的陆地 (grid[i][j] != '1')if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] != '1') {return;}// “沉岛”操作:将当前陆地单元格标记为已访问。// 这里用 '2' 作为标记,也可以用 '0'。grid[i][j] = '2';// 向四个方向递归探索,继续淹没相邻的陆地。dfs(grid, i + 1, j); // 向下dfs(grid, i - 1, j); // 向上dfs(grid, i, j + 1); // 向右dfs(grid, i, j - 1); // 向左}
}

时空复杂度

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

  1. 主循环:外层的两层 for 循环会遍历网格中的每一个单元格,总共 M * N 次,其中 M 是行数,N 是列数。
  2. DFS 访问dfs 函数虽然是递归的,但由于“沉岛”操作(将 ‘1’ 变为 ‘2’),每一个陆地单元格(‘1’)只会被 dfs 成功访问并处理一次
  3. 综合分析
    • 整个算法的过程可以看作是对网格中的每个单元格进行了一次访问。
    • 如果单元格是 ‘0’,主循环访问它,耗时 O(1)。
    • 如果单元格是 ‘1’,主循环访问它,然后 dfs 会接管,访问并标记所有相连的 ‘1’。
    • 最终,每个单元格都被访问了常数次。因此,总的时间复杂度与网格的大小成正比,即 O(M * N)

空间复杂度:O(M * N) (最坏情况)

  1. 主要存储开销:该算法的空间开销主要来自于 DFS 的递归调用栈
  2. 递归深度:调用栈的最大深度取决于 dfs 能够连续深入探索多远。
  3. 最坏情况
    • 考虑一个最坏的情况,即整个网格是一个巨大的、蜿蜒的岛屿,例如蛇形填满整个网格。
    • 在这种情况下,dfs 的递归调用可能会深入到访问完所有 M * N 个单元格后才开始返回。
    • 此时,递归栈的深度会达到 M * N
    • 因此,最坏情况下的空间复杂度为 O(M * N)

注意:尽管最坏情况是 O(M*N),但在许多实际或随机的输入中,递归深度远小于此。不过,在进行复杂度分析时,我们通常考虑最坏情况。

参考灵神

http://www.lryc.cn/news/593888.html

相关文章:

  • MCP实战案例|Trae2.0 一键创建旅行助手并一键部署EdgeOne
  • axios二次封装-单个、特定的实例的拦截器、所有实例的拦截器。
  • Laravel 原子锁概念讲解
  • sqli-labs靶场通关笔记:第34-37关 宽字节注入的其他情况
  • docker Neo4j
  • PDF 编辑器:多文件合并 拆分 旋转 顺序随便调 加水印 密码锁 页码背景
  • Python 进阶知识之numpy库(一)
  • 考研最高效的准备工作是什么
  • 【JDK内置工具】常用工具和实战指令
  • 30天打牢数模基础-决策树讲解
  • Docker在NAS部署MoonTV+OrionTV
  • [Python] -项目实战8- 构建一个简单的 Todo List Web 应用(Flask)
  • 深度学习×第10卷:她用一块小滤镜,在图像中找到你
  • 嵌入式硬件篇---按键
  • 嵌入式硬件篇---继电器
  • USB 2.0 vs USB 3.0:全面技术对比与选择指南
  • 2025《艾诺提亚失落之歌》新手攻略
  • 基于单片机出租车计价器设计
  • DMA控制器(Direct Memory Access Controller)是什么?
  • 用户端功能清单设计指南:从核心模块到优先级排序
  • 面试150 添加与搜索单词--数据结构设计
  • 前端的测试
  • 详解Mysql索引合并
  • 二、Spark 开发环境搭建 IDEA + Maven 及 WordCount 案例实战
  • 每日一题7.20
  • Spring之事务使用指南
  • 【Vue进阶学习笔记】Vue 路由入门指南
  • 18.TaskExecutor获取ResourceManagerGateway
  • 【已解决】GitHub SSH 连接失败解决方案:Permission Denied (publickey) 错误修复指南
  • ant+Jmeter+jenkins接口自动化,如何实现把执行失败的接口信息单独发邮件?