文章目录
什么是回溯法
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
在二叉树系列中,我们已经不止一次,提到了回溯,例如二叉树:以为使用了递归,其实还隐藏着回溯。
回溯是递归的副产品,只要有递归就会有回溯。
所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数。
回溯法的效率
回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法。
因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
那么既然回溯法并不高效为什么还要用它呢?
因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。
此时大家应该好奇了,都什么问题,这么牛逼,只能暴力搜索。
回溯法解决的问题
回溯法,一般可以解决如下几种问题:
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
这块可能初学者还不太理解,后面的回溯算法解决的所有题目中,我都会强调这一点并画图举相应的例子,现在有一个印象就行。
回溯法模板
这里给出Carl总结的回溯算法模板。
在讲二叉树的递归中我们说了递归三部曲,这里我再给大家列出回溯三部曲。
回溯函数模板返回值以及参数
在回溯算法中,我的习惯是函数起名字为backtracking,这个起名大家随意。
回溯算法中函数返回值一般为void。
再来看一下参数,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。
但后面的回溯题目的讲解中,为了方便大家理解,我在一开始就帮大家把参数确定下来。
回溯函数伪代码如下:
void backtracking(参数)
回溯函数终止条件
既然是树形结构,那么我们在讲解二叉树的递归的时候,就知道遍历树形结构一定要有终止条件。
所以回溯也有要终止条件。
什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
所以回溯函数终止条件伪代码如下:
if (终止条件) {
存放结果;
return;
}
回溯搜索的遍历过程
在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。
如图:
回溯函数遍历过程伪代码如下:
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
回溯基础算法汇总
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracking(n,k,1);
return res;
}
public void backTracking(int n, int k, int start){
if(path.size() == k){
res.add(new ArrayList<>(path));
return;
}
for(int i = start; i<=n;i++){
path.add(i);
start++;
backTracking(n,k,start);
path.remove(path.size()-1);
}
}
}
216.组合总和III
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
combinationSum3H(k,n,0,1);
return res;
}
public void combinationSum3H(int k,int n,int sum,int start){
if(path.size() == k){
if(sum == n){
res.add(new ArrayList<>(path));
}
return;
}
for(int i = start; i <= 9; i++){
path.add(i);
sum+=i;
start++;
combinationSum3H(k,n,sum,start);
sum-=i;
path.removeLast();
}
}
}
17.电话号码的字母组合
class Solution {
final private String [] ARR = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
private List<String> res = new ArrayList<>();
private StringBuffer sb = new StringBuffer();
public List<String> letterCombinations(String digits) {
if(digits == null || digits.length() == 0){
return new ArrayList<>();
}
backTrancking(digits,0);
return res;
}
public void backTrancking(String digits,int num){
if(num == digits.length()){
res.add(sb.toString());
return;
}
String s = ARR[digits.charAt(num) - '0'];
for(int i = 0; i < s.length(); i++){
sb.append(s.charAt(i));
backTrancking(digits,num+1);
sb.deleteCharAt(sb.length() - 1);
}
}
}
39. 组合总和
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates == null || candidates.length == 0){
return new ArrayList<>();
}
backTracking(candidates,target,0,0);
return res;
}
public void backTracking(int[] candidates,int target,int sum,int index){
if(sum == target){
res.add(new ArrayList(path));
return;
}
if(sum > target){
return;
}
for(int i = index; i < candidates.length; i++){
path.add(candidates[i]);
sum+=candidates[i];
backTracking(candidates,target,sum,i);
sum-=candidates[i];
path.removeLast();
}
}
}
40.组合总和II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
boolean [] used = new boolean[candidates.length];
Arrays.fill(used,false);
Arrays.sort(candidates);
backTracking(candidates,target,0,0,used);
return res;
}
public void backTracking(int[] candidates, int target, int sum, int start, boolean[] used){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
if(sum > target){
return;
}
for(int i = start; i < candidates.length; i++){
if(i > 0 && candidates[i] == candidates[i-1] && !used[i - 1]){
continue;
}
used[i] = true;
sum += candidates[i];
path.add(candidates[i]);
backTracking(candidates,target,sum,i+1,used);
used[i] = false;
sum -= candidates[i];
path.removeLast();
}
}
}