1.什么是回溯:回溯法是一个纯暴力的搜索方式,有时候一些问题只能靠回溯暴力法来解决
2.适用于什么问题:
1)N个数里面按一定规则找出k个数的集合
2)一个字符串按一定规则有几种切割方式
3)一个N个数的集合里有多少符合条件的子集
4)N个数按一定规则全排列,有几种排列方式
5)N皇后,解数独等等
3.回溯法模板
)回溯返回值以及参数
返回值一般是void
参数一般是根据题目需要再进行确定
2)回溯函数终止条件
什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
3)回溯搜索的遍历过程
这道题会比较像收集路径的题目,但是对于如何把这道题构思为树的结构没有什么概念,初始思路就是第一层for循环表示n=4这四个数,也是k=2中的第一个数,然后通过回溯进入第二层循环,需要一个startindex,因为第二层循环中是n=3三个数,然后k=2作为结束条件就是收集路径的结合大小为2时结束,将路径收集在result集合中。
class Solution {
//跟收集路径很像
List> result = new ArrayList<>();
List path = new ArrayList<>();
public List> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
public void backtracking(int n, int k, int start){
if(path.size()==k){
result.add(new ArrayList(path));
return ;
}
for(int i=start;i<=n;i++){
path.add(i);
backtracking(n,k,i+1);
path.remove(path.size()-1);
}
}
}
这里注意一定要new一个arraylist装入result数组中!不然就会原始的变,result中也变
就是12的时候result也是(12)13的时候result中就变成了(1,3)(1,3),1,4的时候
result中就变成了(1,4)(1,4)(1,4)。
对于将组合问题抽象为树形结构有了更清楚地认知。
每一个节点代表一个for循环
当K的数值比较大时就可以对不满足深度的节点进行剪枝,从而降低运算量。
接下来看一下优化过程如下:
已经选择的元素个数:path.size();
所需需要的元素个数为: k - path.size();
列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
本题跟组合问题基本一致,结束条件仍为path.size()=k,仅是在其中添加判断,只有和为n的才添加进result中。
一些容易犯错误的点:
1,结束条件中一定要写return!!,不然就会stackoverflow
class Solution {
List> result = new ArrayList<>();
List path = new ArrayList<>();
public List> combinationSum3(int k, int n) {
backtracking(k,n,1);
return result;
}
public void backtracking(int k, int n, int startindex){
int sum = 0;
if(path.size()==k){
for(int i:path){
sum = sum+i;
}
if(sum==n){
result.add(new ArrayList(path));
}
return ;
}
for(int i=startindex;i<=10-(k-path.size());i++){
path.add(i);
backtracking(k,n,i+1);
path.remove(path.size()-1);
}
}
}
直接看视频
1.字母和数字如何对应:
/初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
2.树的架构如何理解
3.结束条件就是遍历到叶子节点了,也就是数字String的长度。
class Solution {
//设置全局列表存储最后的结果
List list = new ArrayList<>();
public List letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
//迭代处理
backTracking(digits, numString, 0);
return list;
}
//每次迭代获取一个字符串,所以会设计大量的字符串拼接,所以这里选择更为高效的 StringBuild
StringBuilder temp = new StringBuilder();
//比如digits如果为"23",num 为0,则str表示2对应的 abc
public void backTracking(String digits, String[] numString, int num) {
//遍历全部一次记录一次得到的字符串
if (num == digits.length()) {
list.add(temp.toString());
return;
}
//str 表示当前num对应的字符串
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i));
//c
backTracking(digits, numString, num + 1);
//剔除末尾的继续尝试
temp.deleteCharAt(temp.length() - 1);
}
}
}