【小白笔记】岛屿数量

这是著名的 岛屿数量(Number of Islands)问题,它是一个经典的图论和搜索算法问题。

核心概念解释

  1. 网格 (Grid): 在本题中,指的是一个二维数组(或矩阵),它的元素是 '1'(陆地)或 '0'(水)。
  2. 岛屿 (Island) : '1' 组成的连通区域。这里的"连通"定义为只能通过水平竖直方向(即上下左右)相连。
  3. 搜索算法 (Search Algorithm) : 寻找解决问题路径的方法。在本题中,我们用它来遍历并标记一个完整的岛屿区域。常用的有:
    • 深度优先搜索 (DFS, Depth-First Search): 沿着一个分支一直向下探索,直到不能再深入,然后回溯(退回到上一步)再探索另一分支。
    • 广度优先搜索 (BFS, Breadth-First Search): 从起始点开始,先探索距离它最近的所有节点,然后探索距离次近的节点,逐层向外扩张。

算法思路(以 DFS 为例)

解决这个问题的关键在于:每当我们发现一个新的陆地('1'),就说明我们发现了一个新的岛屿,然后我们需要将这个岛屿的所有部分都标记出来,避免重复计数。

步骤:

  1. 初始化计数器 : 设置一个变量 island_count(岛屿数量),初始值为 0。
  2. 遍历网格 : 逐行逐列地检查网格中的每一个单元格 g r i d [ i ] [ j ] grid[i][j] grid[i][j]。
  3. 发现新岛屿
    • 如果当前单元格是 '1'(陆地),说明我们找到了一个新的岛屿的起点。
    • island_count 加 1
    • 执行 DFS 或 BFS : 从这个起点 g r i d [ i ] [ j ] grid[i][j] grid[i][j] 开始,探索与其相连的所有陆地单元格。
  4. 标记岛屿(DFS 细节)
    • 在 DFS 过程中,每访问到一个陆地单元格 g r i d [ r ] [ c ] grid[r][c] grid[r][c],立即将其标记为 '0'(或任何其他标记,例如 '2'),表示这个部分已经属于一个已被计数的岛屿,以后再遍历到它时就不会被重复计为新岛屿了。
    • 递归地对 g r i d [ r ] [ c ] grid[r][c] grid[r][c] 水平和竖直方向上相邻的单元格执行 DFS,直到到达边界或遇到水('0')。
  5. 返回结果 : 遍历完成后,island_count 的值就是网格中岛屿的总数量。

示例代码(Python,使用 DFS)

python 复制代码
class Solution:
    def numIslands(self, grid: list[list[str]]) -> int:
        if not grid or not grid[0]:
            return 0
        
        rows = len(grid)
        cols = len(grid[0])
        island_count = 0
        
        # 核心:DFS 函数
        # r, c 是当前单元格的行和列索引
        def dfs(r, c):
            # 1. 终止条件(Base Case):
            #   - 越界(超出网格范围)
            #   - 当前单元格是 '0'(水)
            if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
                return
            
            # 2. 标记:将当前的陆地标记为 '0'(沉没),表示已访问,不再重复计数
            grid[r][c] = '0'
            
            # 3. 递归(Recursive Step):向四个方向继续探索
            dfs(r + 1, c) # 向下
            dfs(r - 1, c) # 向上
            dfs(r, c + 1) # 向右
            dfs(r, c - 1) # 向左

        # 遍历整个网格
        for r in range(rows):
            for c in range(cols):
                # 发现一个新的陆地('1'),说明找到了一个新的岛屿
                if grid[r][c] == '1':
                    island_count += 1
                    # 从这个点开始,沉没(标记为 '0')掉整个岛屿
                    dfs(r, c)
                    
        return island_count

复杂度分析

  • 时间复杂度 : O ( M × N ) O(M \times N) O(M×N)。其中 M M M 是行数, N N N 是列数。虽然 DFS 中有递归调用,但网格中的每个单元格最多只会被访问一次(因为一旦访问过,'1' 就会被改为 '0'),所以总时间花费与网格中的单元格总数成正比。
  • 空间复杂度 : O ( M × N ) O(M \times N) O(M×N)。这是在最坏情况下(整个网格都是陆地)递归栈(DFS 调用的深度)所需的空间。

一、总体结构(五大模块)

背诵时心里先有整体框架:

复制代码
1️⃣ 定义函数 → 判空返回 0  
2️⃣ 获取行列、初始化岛屿数  
3️⃣ 定义 DFS(深度优先搜索)  
4️⃣ 遍历网格(双层 for)  
5️⃣ 若遇陆地('1')→ 岛屿 +1 + DFS 沉没

口诀:

"先判空,取行列;岛屿清零 DFS 备;

遍历全图见陆地,沉没递归不重叠。"


二、代码块逻辑与背诵节奏

1️⃣ 开头框架(函数定义与判空)

python 复制代码
class Solution:
    def numIslands(self, grid: list[list[str]]) -> int:
        if not grid or not grid[0]:
            return 0

🪄 记忆要点:

  • 两层判空:if not grid or not grid[0]
  • 这是防止空列表访问出错。

口诀:

"若无网格或无列,直接零岛不多费。"

python 复制代码
if not grid or not grid[0]:
    return 0

看起来简单,但其实是非常关键的一步,背后有两个目的,分别对应不同的异常情况。


🧩 if not grid 检查什么?

这句是在判断:

整个网格是不是空的

也就是说,如果输入的 grid 是空列表 []

比如:

python 复制代码
grid = []

那就表示没有任何行 ,自然也就没有岛屿。

此时直接返回 0,防止后面 len(grid[0]) 出错。

✅ 举例:

python 复制代码
grid = []   # 空的二维网格

如果不加 if not grid,下一步执行 grid[0] 时就会出错:

复制代码
IndexError: list index out of range

if not grid[0] 检查什么?

这一句是在判断:

网格虽然有行,但每一行是空的。

例如:

python 复制代码
grid = [[]]

这种情况下:

  • grid 不是空的(有一行),
  • 但是 grid[0] 是一个空列表(没有列)。

✅ 举例:

python 复制代码
grid = [[]]

此时:

python 复制代码
len(grid)      # 1
len(grid[0])   # 0

虽然看起来"有一行",但其实这一行里没内容,

所以整个网格里也没有岛屿。

于是我们应该返回 0


python 复制代码
if not grid or not grid[0]:
    return 0

等价于:

"如果网格是空的,或者网格中没有任何列,就没有岛屿,直接返回 0。"

也就是说:

  • not grid → 防止空的二维列表;
  • not grid[0] → 防止空的行。

✅ 举个对比总结表:

输入 含义 grid 是否为空 grid[0] 是否为空 返回
[] 完全空 ✅ True ❌(访问会出错) 0
[[]] 有一行但无列 ❌ False ✅ True 0
[['0','1']] 正常网格 ❌ False ❌ False 继续执行

所以这行 if not grid or not grid[0]: return 0

是为了让函数健壮(robust),防止空输入报错,也符合逻辑"没有格子就没有岛屿"。


2️⃣ 初始化基本参数

python 复制代码
        rows = len(grid)
        cols = len(grid[0])
        island_count = 0

口诀:

"行列记录好,岛屿清零跑。"


3️⃣ 定义 DFS 函数

python 复制代码
        def dfs(r, c):
            if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
                return
            grid[r][c] = '0'
            dfs(r + 1, c)
            dfs(r - 1, c)
            dfs(r, c + 1)
            dfs(r, c - 1)

🪄 记忆关键点:

  • base case:越界或水 → return
  • 当前陆地变水(标记访问)
  • 四个方向递归(上下左右)

口诀:

"越界或水停,陆地沉作零;

四向再递归,岛屿一扫清。"

python 复制代码
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
    return

这是 DFS(深度优先搜索)中的"终止条件" ,也叫 Base Case(递归终止判断)

它的作用是:在递归过程中判断当前这个格子是否还需要继续搜索。


🧩 一、整体逻辑:

这一行的意思是:

🚫 "如果当前位置越界,或者不是陆地(是水),那就不要再往下递归了,直接返回。"

也就是告诉 DFS:

"到这里为止,没必要继续找下去了。"


🔍 二、逐个条件解释

条件 含义 举例 为什么要这样判断
r < 0 上越界 在第 0 行再往上走 (r-1 = -1) 已超出网格上边界
r >= rows 下越界 在最后一行再往下走 超出网格下边界
c < 0 左越界 在第 0 列再往左走 (c-1 = -1) 超出网格左边界
c >= cols 右越界 在最后一列再往右走 超出网格右边界
grid[r][c] == '0' 当前是水 该格不是陆地 没必要继续扩展

🌊 三、为什么要这么写?

DFS 是"递归地探索四个方向"的:

python 复制代码
dfs(r + 1, c) # 下
dfs(r - 1, c) # 上
dfs(r, c + 1) # 右
dfs(r, c - 1) # 左

如果没有这句判断:

  • 你会访问网格外的坐标(导致索引错误);
  • 或者一直在水面上递归,陷入死循环。

⚠️ 比如:

复制代码
r = -1  →  grid[-1][c]  # Python 会访问最后一行!错误逻辑

所以这句判断确保 DFS 只在"合法的陆地格子"里活动。


✅ 四、换一种自然语言描述(背诵法)

"如果走出了地图边界,或者当前不是陆地(是水),那就停下来,不再往下找。"

或者口诀记忆:

"越界即停,遇水不行。"


📘 五、可视化理解

假设地图:

复制代码
1 1 0
1 0 0

(0,0) 开始 DFS:

复制代码
(0,0) 是陆地 → 继续找四周
↓
(1,0) 是陆地 → 继续找四周
↓
(2,0) 下越界 → 停!
↑
(0,-1) 左越界 → 停!
→
(0,1) 是陆地 → 继续找
→
(0,2) 是水 → 停!

每次递归都会先执行这句"安全检查",防止访问非法位置或重复访问水。


✨ 六、总结一句话

这一行就是 DFS 的"安全护栏"。

它确保递归只在地图范围内、陆地格上进行,

一旦越界或遇水,立即返回。

非常棒的问题👏,你已经抓住了 DFS 在网格中移动的核心逻辑


DFS 的核心思想

DFS(深度优先搜索)是**"从一个点出发,能走的方向都走一遍"**。

在这个题中,每个陆地 '1' 都有 上下左右 四个可能的连接方向。


🧭 二、这四行代码表示"向四个方向探索"

python 复制代码
dfs(r + 1, c) # 下:行号 +1(往下移动一行)
dfs(r - 1, c) # 上:行号 -1(往上移动一行)
dfs(r, c + 1) # 右:列号 +1(往右移动一列)
dfs(r, c - 1) # 左:列号 -1(往左移动一列)
  • r 表示当前所在的行号
  • c 表示当前所在的列号
  • 加减操作就是"往某个方向移动一步"

🧩 三、那如果在边角呢?(比如 (0, 0) 左上角)

假设当前在最左上角 (0, 0),那么:

调用 实际坐标 判断结果
dfs(r+1, c)(1,0) ✅ 在网格里,可以继续
dfs(r-1, c)(-1,0) ❌ 越界,被 if r < 0 拦截
dfs(r, c+1)(0,1) ✅ 在网格里,可以继续
dfs(r, c-1)(0,-1) ❌ 越界,被 if c < 0 拦截

🔒 四、所以为什么还要写四个方向?

因为:

  • DFS 的逻辑是 "无论在哪,都尝试四个方向"

  • 但实际执行时,会有边界条件:

    python 复制代码
    if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
        return

    → 这行保证了:出界或遇到水都会立即停止,不会报错或继续下探。


总结口诀:

DFS 探四方,边界来阻挡;

走陆地沉没,遇水立刻返。


4️⃣ 主循环:遍历整个网格

python 复制代码
        for r in range(rows):
            for c in range(cols):
           
                if grid[r][c] == '1':
                    island_count += 1
                    dfs(r, c)

🪄 记忆重点:

  • 遍历每个格子
  • 遇到 '1'(陆地) → 岛屿数 +1,然后沉没整岛

口诀:

"双层循环行列转,陆地发现岛加一;

DFS 沉没不复现,整座岛屿全归零。"


5️⃣ 返回结果

python 复制代码
        return island_count

口诀:

"所有陆地扫完毕,返回岛数最合理。"


📘 三、完整背诵结构图(逻辑树)

复制代码
numIslands(grid):
    ├── 判空 → return 0
    ├── rows, cols, island_count
    ├── 定义 dfs(r, c):
    │       ├── 越界 or 水 → return
    │       ├── 标记当前为 '0'
    │       ├── 四方向递归
    ├── 遍历 r, c:
    │       ├── 若 '1' → 岛屿+1
    │       ├── dfs(r, c)
    └── 返回 island_count

✨ 附:极简背诵版代码(逻辑骨架)

python 复制代码
class Solution:
    def numIslands(self, grid):
        if not grid or not grid[0]:
            return 0
        rows, cols = len(grid), len(grid[0])
        count = 0

        def dfs(r, c):
            if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
                return
            grid[r][c] = '0'
            dfs(r+1, c); dfs(r-1, c); dfs(r, c+1); dfs(r, c-1)

        for r in range(rows):
            for c in range(cols):
                if grid[r][c] == '1':
                    count += 1
                    dfs(r, c)
        return count
相关推荐
CLubiy3 小时前
【研究生随笔】Pytorch中的线性代数
pytorch·python·深度学习·线性代数·机器学习
Larry_Yanan3 小时前
QML学习笔记(四十三)QML与C++交互:上下文属性暴露
c++·笔记·qt·学习·ui·交互
reasonsummer3 小时前
【办公类-115-02】20251018信息员每周通讯上传之文字稿整理(PDF转docx没有成功)
python·pdf
材料科学研究3 小时前
深度学习物理神经网络(PINN)!
python·深度学习·神经网络·pinn
兰文彬3 小时前
Pytorch环境安装指南与建议
人工智能·pytorch·python
哦你看看3 小时前
学习Python 03
开发语言·windows·python
后端小张3 小时前
[AI 学习日记] 深入解析MCP —— 从基础配置到高级应用指南
人工智能·python·ai·开源协议·mcp·智能化转型·通用协议
天青色等烟雨..3 小时前
AI+Python驱动的无人机生态三维建模与碳储/生物量/LULC估算全流程实战技术
人工智能·python·无人机
新子y3 小时前
【小白笔记】岛屿的周长(Island Perimeter)
笔记·python