所用代码 java
回溯:是一种纯暴力的搜索,搜索所有的情况,效率并不高,有些问题没办法用for循环暴力求出,只能回溯
应用场景:
- 组合问题(无序) 1234 (12 13 14 22 23 24 33 34)
- 切割问题 - - - -
- 子集问题
- 排列问题(有序)
- 棋盘问题 (N皇后、解数独)
回溯比较难懂,但都可以抽象为一棵n叉树,树的宽度就是处理结点集合的大小(for),树的深度就是递归的深度(递归)
回溯模板:
void backtracking(参数){
if(终止条件){
收集结果,通常都是在叶子结点 ;
return ;
}
for(集合元素集,遍历每一个元素){
处理结点 ;
递归函数 ;
回溯操作,撤销处理结点的操作 ;
}
}
组合 LeetCode 77
题目链接:组合 LeetCode 77 - 中等
思路
无。
回溯三部曲:
- 回溯函数参数返回值
- 确定终止条件
- 单层递归逻辑
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtarcking(n, k, 1);
return res;
}
public void backtarcking(int n, int k, int startIndex){
// 回溯终止条件
if (path.size() == k){
// 错误的写法,这种写法传的是path的引用地址,会跟随着递归一直变化
// res.add(path);
// 正确的写法应该是把path赋值到一个新的list中
List<Integer> temp = new ArrayList<>(path);
res.add(temp);
return;
}
// 单层递归逻辑
for (int i = startIndex; i <= n; i++) {
path.add(i);
// 下一层起始的位置是i+1
backtarcking(n, k, i+1);
// 回溯,把最后加的值给剪掉
path.remove(path.size()-1);
}
}
}
剪枝操作:
假如我们n=4,k=3,我们在搜索的时候就没必要i从3开始了,因为从3开始就只有两个数,同样的再往深度递归的时候也没必要搜索那么多一定不符合的条件。
- n=4,k=3
- 那么我们还需要
k - path.size()
个数,假如path里面已经存了一个数,那么就还需要3 - 1 = 2
个数 - 所以说我们至多能从
n - (k - path.size()) + 1
下标开始,也就是我的下标应该从比这个数小的开始
剪枝关键代码:
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
// 下一层起始的位置是i+1
backtarcking(n, k, i+1);
// 回溯,把最后加的值给剪掉
path.remove(path.size()-1);
}
总结
写回溯的是主要是单层的递归逻辑,以及回溯的操作,有时候有些步骤有点难相通,可以多打印日志来查看哪里有问题,然后画个图更容易了解。