【回溯理论基础】
1、本质:穷举,与递归结合使用,通常理解为树形结构
2、解决问题类型
3、回溯模板(与递归结合)
- 确定参数(返回值一般为void,用成员变量记录结果)
- 确定终止条件(何时记录结果)
- 确定单层递归逻辑(遍历所有子节点,处理子节点,递归子节点,回溯恢复现场)
public void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (遍历所有子节点)) {
处理子节点;
backtracking(路径,选择列表); // 递归
回溯,恢复现场
}
}
【77. 组合】
思路:用for循环遍历所有子节点,先将子节点加入路径,再将子节点放入递归记录该子节点对应的所有结果,再在子节点递归结束后恢复当前节点对应路径,继续遍历下一个子节点
优化(剪枝):如果当前节点path.getLast()的最大路径长度(剩下可用元素 + 当前路径长度) < k,直接return即可
相似题目:113. 路径总和 II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(k, 1, n);
return res;
}
public void backtracking(int k, int begin, int end){
// 如果深度达到k则记录和返回
if (path.size() == k){
res.add(new LinkedList(path)); // 直接add的是path的引用,会随着path改变发生变化,因此需要复制一份再add
return;
}
// 剪枝:如果当前节点path.getLast()的最大路径长度(剩下可用元素 + 当前路径长度) < k,直接return即可
if (path.size() > 0 && end - path.getLast() + path.size() < k) return;
// 左闭右闭,遍历当前节点的所有子节点,记录所有子节点分支的所有结果
// 如果长度还没达到k,但是已经没有可用元素(begin>end),则不进入for循环,函数执行完毕直接返回
for (int i = begin; i <= end; i++){
path.addLast(i); // 先把当前子节点加入路径
backtracking(k, i + 1, end); // 再让递归记录该子节点下所有分支的结果
path.removeLast(); // 最后把该子节点从路径移除,让新的子节点进来
}
}
}
时间复杂度: O(2^n × n),所有n个元素都有2种选择即放/不放进结果中O(2^n),复制O(n)
空间复杂度: O(n)