代码随想录算法训练营Day22
回溯法
理论基础
- 回溯法解决的问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
- 如何理解回溯法
抽象为树型结构,集合的大小构成了树的宽度,递归的深度构成的树的深度。 - 回溯模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
组合问题
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
直接的想法是一个双层的for循环,但是当k=50时,这种想法就不可行了,因此需要其他方法,这就是回溯算法
class Solution {
List<List<Integer>> result= new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
public void backtracking(int n,int k,int startIndex){
if (path.size() == k){
result.add(new ArrayList<>(path));
return;
}
for (int i =startIndex;i<=n;i++){
path.add(i);
backtracking(n,k,i+1);
path.removeLast();
}
}
}
组合问题的剪枝操作
例如n=4,k=4,此时有很多组合是不可能的,剪枝操作即将这些可能性省略,从而优化代码
代码中的n-(k-path.size())+1是怎么来的?
path.size():已经选择的元素的个数, k-path.size():还要选的元素的个数,n-(k-path.size()):下标的最大值,可以带入进n=4,k=3验证一下
class Solution {
List<List<Integer>> result= new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
public void backtracking(int n,int k,int startIndex){
if (path.size() == k){
result.add(new ArrayList<>(path));
return;
}
for (int i =startIndex;i<=n-(k-path.size())+1;i++){ //这里的n-(k-path.size())+1是怎么来的,画图,找规律
path.add(i);
backtracking(n,k,i+1);
path.removeLast();
}
}
}
组合综合3
class Solution {
//注意这里的n是元素和,k是元素个数,和组合不一样
List<Integer> path = new ArrayList<>();
List<List<Integer>> result= new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k,n,0,1);
return result;
}
void backtracking(int k,int n,int sum,int startIndex){
if(sum>n){//剪枝
return;
}
if(sum == n && path.size() == k){
//result.add(path);//这里是错的,其实加的是同一个引用,后续path变化会影响result里的内容
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i <= 9 - (k - path.size()) + 1;i++){//这里的剪枝剪的是集合个数
path.add(i);
sum+=i;
backtracking(k,n,sum,i+1);
sum-=i;//这里别忘了
path.removeLast();
}
}
}
电话号码的字母组合
class Solution {
//设置全局列表存储最后的结果
List<String> list = new ArrayList<>();
StringBuilder temp = new StringBuilder();
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
//初始对应所有的数字
backTracking(digits, 0);
return list;
}
public void backTracking(String digits, int num) {//这里的num与之前的startIndex不同,这里是遍历到digits中的哪一位,不用startIndex做去重,因为从两个集合取元素
if (num == digits.length()) {
list.add(temp.toString());
return;
}
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i));
backTracking(digits, num + 1);
temp.deleteCharAt(temp.length() - 1);
}
}
}

浙公网安备 33010602011771号