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

核心概念解释
- 网格 (Grid): 在本题中,指的是一个二维数组(或矩阵),它的元素是 '1'(陆地)或 '0'(水)。
- 岛屿 (Island) : '1' 组成的连通区域。这里的"连通"定义为只能通过水平 或竖直方向(即上下左右)相连。
- 搜索算法 (Search Algorithm) : 寻找解决问题路径的方法。在本题中,我们用它来遍历并标记一个完整的岛屿区域。常用的有:
- 深度优先搜索 (DFS, Depth-First Search): 沿着一个分支一直向下探索,直到不能再深入,然后回溯(退回到上一步)再探索另一分支。
- 广度优先搜索 (BFS, Breadth-First Search): 从起始点开始,先探索距离它最近的所有节点,然后探索距离次近的节点,逐层向外扩张。
算法思路(以 DFS 为例)
解决这个问题的关键在于:每当我们发现一个新的陆地('1'),就说明我们发现了一个新的岛屿,然后我们需要将这个岛屿的所有部分都标记出来,避免重复计数。
步骤:
- 初始化计数器 : 设置一个变量
island_count
(岛屿数量),初始值为 0。 - 遍历网格 : 逐行逐列地检查网格中的每一个单元格 g r i d [ i ] [ j ] grid[i][j] grid[i][j]。
- 发现新岛屿 :
- 如果当前单元格是 '1'(陆地),说明我们找到了一个新的岛屿的起点。
- 将
island_count
加 1。 - 执行 DFS 或 BFS : 从这个起点 g r i d [ i ] [ j ] grid[i][j] grid[i][j] 开始,探索与其相连的所有陆地单元格。
- 标记岛屿(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')。
- 返回结果 : 遍历完成后,
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 的逻辑是 "无论在哪,都尝试四个方向";
-
但实际执行时,会有边界条件:
pythonif 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