图解题库 / 网格 · DFS

200. 岛屿数量

中等 含交互动画

学完这道你应该能: 说清它为什么用「DFS」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

网格里 1 是陆地、0 是水。上下左右相连的 1 算同一座岛,问共几座岛?

grid = 见下方(3×3)
输出 = 2

思路解析

地图:绿色是陆地,蓝色是水。肉眼看:左上角连成一片是一座岛,右下角单独一格是另一座。

从左到右、从上到下扫网格。每碰到一个还没访问过的陆地,岛屿数就 +1,然后用 DFS 把和它相连的陆地全部标记为已访问,这样同一座岛就不会被重复数。

扫描 (0,0):扫到 (0,0) 是陆地,而且没访问过。按代码的顺序:先对它 DFS,把整座岛淹掉,等 DFS 结束再给岛屿数 +1。

DFS · 访问 (0,0):把 (0,0) 标记为已访问(变灰)。然后检查它的上下左右,看哪些也是陆地。

DFS · 右边 (0,1):(0,0) 右边的 (0,1) 是陆地,走过去,继续对它做 DFS。

DFS · 访问 (0,1):标记 (0,1)。它右边是水、下边是水,没有新陆地可走,退回 (0,0) 继续看其它方向。

DFS · 下边 (1,0):(0,0) 下边的 (1,0) 也是陆地,走过去。

DFS · 访问 (1,0):标记 (1,0)。它周围也没有新陆地了。这座岛的 3 格全部访问完,DFS 结束。

第一座岛完成 · count+1:整座岛(3 格)都标记完了,DFS 返回。这时岛屿数才 +1 = 1(对应代码里 dfs(...) 之后的 ans += 1)。继续往后扫描。

继续扫描 · 全是水:接着扫 (0,2)、(1,1)、(1,2)、(2,0)、(2,1)——它们要么是水、要么已访问,全部跳过。

扫到 (2,2):扫到 (2,2) 是陆地,又没访问过。同样:先对它 DFS。

DFS · 访问 (2,2) · count+1:标记 (2,2)。上边是水、左边是水、右和下都越界,是孤零零一座岛,DFS 立刻结束。岛屿数 +1 = 2。

扫描结束:整个网格扫完,一共触发了 2 次「发现新岛」,答案就是 2。

DFS 沿着一个方向一直深入,直到走不动(碰到水或边界)才回头换方向。把一座岛「淹没」靠的就是它。

岛屿最大面积、被围绕的区域、单词搜索……所有「网格里找连通区域」的题,都是这套网格 DFS/BFS。

参考代码(Python)

Python
1def numIslands(self, grid):
2    ans = 0
3    for i in range(rows):
4        for j in range(cols):          # 扫描每一格
5            if grid[i][j] == class="cl-str">"1" and not visited[i][j]:
6                self.dfs(grid, i, j)   # 淹没整座岛
7                ans += 1               # 岛屿数 +1
8    return ans

复杂度分析

  • 时间复杂度:O(m×n) —— 每个格子最多访问一次
  • 空间复杂度:O(m×n) —— 递归栈最坏铺满整张网格

套路模板

主循环找未访问的陆地 → DFS 淹掉一整片 → 计数 +1。换 BFS 就把递归改成队列。

Python
1for i in range(m):
2    for j in range(n):
3        if grid[i][j]==class="cl-str">"1" and not visited[i][j]:
4            dfs(i, j); ans += 1
5def dfs(i, j):
6    visited[i][j] = True
7    for di,dj in [(0,1),(1,0),(0,-1),(-1,0)]:
8        ni,nj = i+di, j+dj
9        if 不越界 and not visited and grid==class="cl-str">"1": dfs(ni,nj)

易错点

  • 递归前不判越界 → ✅ 先判 0<=ni<m and 0<=nj<n(边缘格子的邻居会出界)
  • 忘了标记 visited → ✅ 进 dfs 第一件事就标记(否则无限递归 / 重复数同一座岛)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →322. 零钱兑换 ← 返回题库