回溯题频繁、面试友好,一个模板能解决大部分。
模板:每个决策点尝试每个合法选择→递归→撤销(pop)。三步曲:"选→递→回"。
子集:每个元素选或不选,2^n个输出。每个节点都收集path。
组合:子集+大小为k约束(path.size>k剪枝,或靠start指针限制)。
排列:顺序有意义。维护used[]或原地交换。n!个输出。
剪枝:排序后跳重复、剩余不够达标(子集和)就剪、对比已知最优值剪。
一个模板:`void dfs(path, state){ if(终止){收集path; return;} for(choice in 可选(state)){ 做选择; dfs(path+choice, 下一状态); 撤销; } }`。子集每次递归都收集(无终止门),排列用visited[]跳已选,组合内循环从i+1开始。重复数据我先排序再`if i>start && nums[i]==nums[i-1] 跳`。剪枝决定过不过TLE——子集和里已超就砍、N皇后里用集合存对角线O(1)判断。递归树形状告诉我最坏复杂度:子集2^n、排列n!。
显式撤销比每次复制path易于推理。
visited[]比从list里删O(1),从list删O(n)。
即答侠给了子集/排列/组合的单页cheat sheet,附带去重模板。
回溯就是DFS,多了显式撤销步骤所以能探下一个选择。DFS是更宽的概念。
任何单调上界能证明"这支没戏"时。先排序常常能开启剪枝机会。