主题
先说总体思路,然后说 DFS 的细节。
总体思路
看示例 2:
假设你是哥伦布。先从左上角开始,把第一个岛全部插上旗子🚩,这里用
⚠注意:在岛上,你只能左右上下走,不能斜方向走。
继续遍历,寻找其他的岛屿,也就是
继续遍历,寻找其他的岛屿。找到
如果没有
如何实现?
一旦我们发现
每一步可以往左右上下四个方向走,也就是
这四个格子。
如果到达一块未发现的陆地格子,就插上旗子🚩,把
如果
⚠注意:DFS 的过程中,最重要的是不能重复访问之前访问过的格子。
比如从左上角
怎么避免重复访问?本题的做法是把访问过的格子都插上旗子🚩。例如从
答疑
问:二叉树的递归和网格图的递归有何区别?
答:列表总结如下。
| 二叉树 | 网格图 | |
|---|---|---|
| 递归入口 | 根节点 | 岛屿第一行最左边的陆地 |
| 递归方向 | 左儿子和右儿子 | 左右上下的相邻陆地 |
| 递归边界 | 空节点(或者叶节点) | 出界、遇到水或者旗子 |
问:如何理解递归?
答:递归的思想是,假设你是一家公司的老板,你不需要万事亲力亲为,而是拆解问题,交给下属去做。对于这题来说,就是从岛屿的某个位置登陆插旗,然后一分为四,把探索整个岛屿的任务交给其他人去处理,自己只需处理好第一步就行。
问:我可以先判断
问:不行,比如
问:我可以把访问过的
答:可以。相当于把访问过的位置变成水。其实,改成除了
问:我可以把这行代码 grid[i][j] = '2' 写在
答:不行。这样写会先执行递归,比如从左上角
问:DFS 中能否只考虑往右走和往下走?
答:不行,比如蚊香形的岛屿,在探索的时候必须具备左右上下走的能力。
python
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n = len(grid), len(grid[0])
def dfs(i: int, j: int) -> None:
# 出界,或者不是 '1',就不再往下递归
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
return
grid[i][j] = '2' # 插旗!避免来回横跳无限递归
dfs(i, j - 1) # 往左走
dfs(i, j + 1) # 往右走
dfs(i - 1, j) # 往上走
dfs(i + 1, j) # 往下走
ans = 0
for i, row in enumerate(grid):
for j, c in enumerate(row):
if c == '1': # 找到了一个新的岛
dfs(i, j) # 把这个岛插满旗子,这样后面遍历到的 '1' 一定是新的岛
ans += 1
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
auto dfs = [&](this auto&& dfs, int i, int j) -> void {
// 出界,或者不是 '1',就不再往下递归
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] != '1') {
return;
}
grid[i][j] = '2'; // 插旗!避免来回横跳无限递归
dfs(i, j - 1); // 往左走
dfs(i, j + 1); // 往右走
dfs(i - 1, j); // 往上走
dfs(i + 1, j); // 往下走
};
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') { // 找到了一个新的岛
dfs(i, j); // 把这个岛插满旗子,这样后面遍历到的 '1' 一定是新的岛
ans++;
}
}
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 和 分别是 的行数和列数。由于 DFS 中修改了 grid[i][j] = '2',当我们访问一个访问过的格子时,会触发if grid[i][j] != '1': return。只有首次访问一个格子时,才会继续递归,其余情况不会继续递归。每次插上一个旗子只需要的时间,插上至多 个旗子,就需要 的时间。 - 空间复杂度:
。最坏情况下,对于蛇形陆地、蚊香型陆地等,递归需要 的栈空间。
思考题
- 构造一个
,让上面代码的递归深度达到最大。 - 如果调整四个方向的递归顺序(比如顺序为右下左上),递归路径的形状会发生什么变化?
欢迎在评论区分享你的思路。
专题训练
见下面的网格图题单。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府