day22|| 回溯算法 ||lc77组合 ||lc216组合总和III || lc17电话号码的字母组合
day22_回溯算法
基础知识
回溯和递归是相辅相成的, 只要有递归 就会有回溯 通常在递归函数的下面出现递归
通常用于解决 组合问题 切割问题 子集问题 排列问题 棋盘问题(n皇后)
**组合 : [1, 2] 和 【2, 1】是相同的组合 **
**排列: 【1,2】 和 【2,1】 是两个不同的排列 **
回溯法 其实是一个纯暴力的搜索算法
比如 给你1 2 3 4 在这里面找到大小为2的组合 那么组合分别是多少
切割问题: 给你一个字符串 问有几种切割方法 或者如何保证子串 都是回文子串 几种方式
回溯法都可以抽象成n叉树结构
树的宽度就是回溯法处理的集合的大小 使用for循环
树的深度就是递归的深度 因为递归一定是有终止的 一层一层向上返回
回溯模版
**一般来说 回溯的函数是没有返回值的 一般情况起名叫backtracking **
参数的话 一般情况是非常多的 一次性无法完全确定
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

void backtracking(参数){
if(终止条件){
收集结果;
return;
}
for(选择:本层集合中元素(树中节点孩子的数量就是集合大小)){
处理节点;
backtracking(路径, 选择列表);
回溯,撤销处理结果;
}
}
其中 for循环就是便利集合区间 , 一个节点有多少孩子 这个for就执行多少多少次
lc77_组合
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
此时 我们会想到一个非常非常普通暴力的解法 就是k=2的时候 使用两层for暴力
但是当k=40 50 60的情况下呢? 难道要写60个for??
使用回溯算法
回溯算法 其实就是通过递归来控制有多少for循环 递归里面的每一次其实就是一个for循环
下一次递归就是下一层for循环
回溯三部曲
- 确定递归函数的参数和返回值
一般情况 返回值都是void 极个别情况才会有返回值 函数名通常叫backtracking
- 确定终止条件
到了叶子节点也就是路径大小为2的时候
- 确定单层搜索逻辑
startIndex用来控制每个for循环从哪里开始的
下一层startIndex应该传入i + 1
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
backtracking(n, k, 1, res, path);
return res;
}
public void backtracking(int n, int k, int sIndex, List<List<Integer>> res, List<Integer> path){
if(path.size() == k){
res.add(new ArrayList<>(path));
return;
}
for(int i = sIndex; i <= n; i++){
path.add(i);
backtracking(n, k, i + 1, res, path);
path.remove(path.size() - 1); //移除最后一个
}
}
}
其中这个代码可以进行剪枝操作
假设n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了
注意i,就是for循环里选择的起始位置。
接下来看一下优化过程如下:
- 已经选择的元素个数:path.size();
- 还需要的元素个数为: k - path.size();
- 在集合n中至多要从该起始位置 : 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]。
所以剪枝只需要控制for循环的范围就可以
i <= n - (k - path.size()) + 1;
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
lc216_组合总和III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
跟上一个题lc77的区别就是 本题给定了和的限制 让我们求和为n
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
backtracking(k, n, 1, 0, res, path);
return res;
}
private void backtracking(int k, int n, int sIndex, int sum, List<List<Integer>> res, List<Integer> path) {
if(sum > n) return;
if(path.size() == k){
if(sum == n) res.add(new ArrayList<>(path));
return;
}
for(int i = sIndex; i <= 9; i++){
path.add(i);
sum += i;
backtracking(k, n, i + 1, sum, res, path);
path.remove(path.size() - 1);
sum -= i;
}
}
剪枝:
** if(sum > n) return;**
i <= 9 - (k - path.size()) + 1;
lc17_电话号码的字母组合(手撕高频)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
- 输入:"23"
- 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

首先要解决映射的问题, 数字2 对应 abc 数字3 对应def .....
String[] numString = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
为什么要0和1也要呢 因为要对应下标 2就是对应abc
这个num是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。 然后收集结果,结束本层递归。
class Solution {
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
if(digits == null || digits.length() == 0) return res;
String[] numString = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
backtracking(digits, numString, 0, res, path);
return res;
}
public void backtracking(String d, String[] numString, int num, List<String> res, StringBuilder path){
if(num == d.length()){
res.add(path.toString());
return;
}
String str = numString[d.charAt(num) - '0']; // 获取当前数字的字母映射
for(int i = 0; i < str.length(); i++){
path.append(str.charAt(i));
backtracking(d, numString, num + 1, res,path);
path.deleteCharAt(path.length() - 1);
}
}
}

浙公网安备 33010602011771号