本人大一蒟蒻经常在写动态规划递归版本时遇到for循环套递归,总是非常的懵逼,经过长达xx小时的思考我悟了!
先看最经典递归套for循环->排列和组合
int* path;
int pathsize;
void dfs(int n, int k, int index) {
if (pathsize == k) {
for (int i = 0; i < pathsize; i++) {
printf(“%d”, path[i]);
}
printf(“\n”);
return;
}
for (int i = index; i <= n; i++) {
path[pathsize++] = i;
dfs(n, k, i + 1);
pathsize--;
}
}
-
void dfs2(int n, int k, int index, bool* vis) {
if (pathsize == k) {
for (int i = 0; i < pathsize; i++) {
printf(“%d”, path[i]);
}
printf(“\n”);
return;
}for (int i = 1; i <= n; i) {
if (vis[i]) continue;
vis[i] = true;
path[pathsize] = i;
dfs2(n, k, i + 1, vis);
vis[i] = false;//回溯,撤销的是对同一层的影响,也就是你要进另一条分岔路了,所以要吃“后悔药”将影响消除
pathsize–;
}
}
其实递归外面套一个for循环就是尝试“同一层”的所有可能!
比如第一个组合数就是path在pathsize层取(index到n)的所有可能,排列数就是path在pathsize层取(1到n)的所可能。(因为不能重复取所以要去重)
再提一嘴,回溯消除的对同一层的影响,也就是你走完某个支路了要走另一条路时就需要回溯.
举一反三,最长上升子序列也有类似for循环套递归的结构,如何理解?
很简单就是尝试所有以(1到index)结尾最长递增子序列的长度,最后再取呢个最长的。
主播主播我现在懂for循环的作用哩,啥时候才用for循环套递归呢?
很简单,就是你发现当前这个位置的状态必须通过前面多个状态才能推导出来,就需要for循环去遍历前面多个状态。
本人语文实在是不好,没看懂可以看看我拿ai做的的总结
一、for循环套递归的本质
- 横向展开(同层级探索)
for 循环的作用是 枚举当前层级的所有候选选项,比如: - 组合问题:
[1,2,3]
中选 2 个数时,第一层 for 循环会尝试以1
、2
、3
开头的所有组合 -
排列问题:for 循环会尝试所有未被使用的数字
-
纵向深入(递归到下一层)
每次递归调用会进入下一层决策(如pathsize+1
),处理子问题。
二、何时需要用 for 循环套递归?
当问题满足以下特征时:
1. 多分支决策:当前步骤有多个候选选项需要尝试(如排列/组合的数字选择)。
2. 状态依赖前序多个子状态:比如 LIS 问题中,dp[i]
需要比较所有 j < i
的 dp[j]
。
三、经典问题对比理解
问题类型 | for 循环作用 | 递归参数变化 | 是否需要回溯 |
---|---|---|---|
组合问题 | 枚举当前可选的起始数字(避免重复) | index+1 限制选择范围 |
是(路径回溯) |
排列问题 | 枚举所有未被使用的数字 | 通过 vis[] 标记已用数字 |
是(标记回溯) |
LIS 问题 | 枚举所有可能的前驱状态 | 比较所有 j < i 的 dp[j] |
否(纯动态规划) |
四、实际应用场景举例
- 需要 for 循环的场景:
- 组合/子集/排列:需要枚举选择。
- 树形DP(如二叉树路径问题):遍历所有子节点。
- 图遍历(如DFS邻接表):遍历相邻节点。
-
分段决策(如分割回文串):尝试所有分割点。
-
不需要 for 循环的递归:
- 斐波那契数列:只依赖前两个固定状态。
- 线性结构(如链表反转):每次处理一个固定节点。
五、终极判断技巧
问自己:当前步骤的决策是否需要尝试多个选项?
- 如果答案是”是” → 需要 for 循环(例如:”这一位可以选 A/B/C…”)
- 如果答案是”否” → 直接递归(例如:”只能往左走”)
六、代码模板提炼
void dfs(当前状态) {
if (终止条件) {
记录结果;
return;
}
for (所有可选选项) {
if (选项无效) continue; // 剪枝
修改状态; // 选择当前选项
dfs(新状态); // 进入下一层
恢复状态; // 回溯(关键!)
}
}
你的 LIS 理解完全正确:for 循环就是在找所有可能的前驱状态。这种思维可以推广到 90% 的回溯/DP问题。继续保持这种”横向枚举+纵向递归”的思考方式!