回溯算法的思路总结
基本步骤
这种回溯类型的题目老是莫名其妙的可以做出来,但是不清楚具体的逻辑
下次做回溯类型的题目可以画出这个决策树
回溯 = 在这棵树上 DFS 深度优先遍历,走所有可能的路径
(sum=0, path=[])/ \+2 / \ +3/ \(sum=2, [2]) (sum=3, [3])/ \ |+2 / \ +3 +2/ \ |
(sum=4, [2,2]) (sum=5, [2,3]) (sum=5, [3,2])|+2? → sum=6 >5 → 剪枝
面对回溯题,我应该如何思考
-
想清楚每一步要做什么选择,这决定了 for 循环的内容
- 全排列:当前要填第
i
个位置,我能选“还没用过的数” - 组合总和:当前要选一个数加入
path
,我能选“从start
开始的数”(避免重复组合)
- 全排列:当前要填第
-
设计递归的函数,我需要记住哪些信息才能继续走?dfs(…) 的参数。
path
:当前走了哪些路(当前组合)sum
/target
:当前状态(如和、深度等)start
:从哪开始选(避免重复)used[]
:哪些元素已用(排列问题)
-
写“终止条件” —— 什么时候停下来?
- 成功,
sum == target
→ans.add(new ArrayList<>(path))
- 失败,
sum > target
→ 直接返回(剪枝)
- 成功,
-
写“循环 + 选择 + 递归 + 撤销”
-
for (每个可选的选项) {if (剪枝) continue;// 1. 做选择(记录状态)path.add(x);// 2. 递归:走进下一层dfs(新状态);// 3. 撤销选择(恢复现场)path.remove(path.size() - 1);}
-
如何看懂嵌套的函数调用
大多数人的误区是:
我要跟着 dfs
一层层进去,看它怎么执行……”
结果越跟越晕
正确做法是:
假设 dfs
能搞定“从当前状态出发的所有解” ,你只关心“我做了一个选择后,剩下的交给 dfs
去处理”
你只相信 dfs 会把“以当前 path 为前缀的所有解”都找出来,只要处理好当前的选择问题,剩下的就不要考虑了。
同时最好画一下决策树,这样能够更有利于判断。