第七章 回溯算法part01
理论基础
其实在讲解二叉树的时候,就给大家介绍过回溯,这次正式开启回溯算法,大家可以先看视频,对回溯算法有一个整体的了解。
题目链接/文章讲解:https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
视频讲解:https://www.bilibili.com/video/BV1cy4y167mM
回溯基础
:::tips
1.回溯法也可以叫做回溯搜索法
只要有递归就会有回溯,回溯是暴力递归,本质是穷举,穷举所有可能,然后选出我们想要的答案,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。
2.回溯相关问题
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
3.所有回溯法的问题都可以抽象为树形结构(N叉树)
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
4.回溯模板
//1.回溯函数模板返回值以及参数,返回值一般为void,回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数
void backtracking(参数) {
//2.回溯函数终止条件
if (终止条件) {
存放结果;
return;
}
//3.回溯搜索的遍历过程模板
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
:::
77. 组合
对着 在 回溯算法理论基础 给出的 代码模板,来做本题组合问题,大家就会发现 写回溯算法套路。
在回溯算法解决实际问题的过程中,大家会有各种疑问,先看视频介绍,基本可以解决大家的疑惑。
本题关于剪枝操作是大家要理解的重点,因为后面很多回溯算法解决的题目,都是这个剪枝套路。
题目链接/文章讲解:https://programmercarl.com/0077.%E7%BB%84%E5%90%88.html
视频讲解:https://www.bilibili.com/video/BV1ti4y1L7cv
剪枝操作:https://www.bilibili.com/video/BV1wi4y157er
216.组合总和III
如果把 组合问题理解了,本题就容易一些了。
题目链接/文章讲解:https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html
视频讲解:https://www.bilibili.com/video/BV1wg411873x
17.电话号码的字母组合
本题大家刚开始做会有点难度,先自己思考20min,没思路就直接看题解。
题目链接/文章讲解:https://programmercarl.com/0017.%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.html
视频讲解:https://www.bilibili.com/video/BV1yV4y1V7Ug
暴力递归的剪枝操作
例如组合问题 n=4 k=3
最多遍历到位置 i 就停止向后遍历,做剪枝优化。
77. 组合
题目链接
https://leetcode.cn/problems/combinations/description/
解题思路
k=2 俩个for循环可以解决 k=3 三个for循环解决 k=50 50个for循环解决问题,所以这代码都没法写出来,只能使用暴力递归法解决这类问题,能穷举写出来就不错了,可以做剪枝优化。
组合问题,回溯模板解题,要画图 模拟N插树,k是树的深度 n是树的宽度,每次取什么元素 以及剩下什么元素可取,最后取的元素如果等于k就是收集的结果,然后在回溯到上一层删除掉元素,在取元素。
先画出来树,然后进行剪枝优化 i <=n-(k-path.size())+1 i至多遍历到这个位置,后面的位置都不满足达到k个元素了,就剪枝。
本题 i是从1开始 i=1 循环结束是<=
code
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(1,n,k);
return res;
}
public void backtracking(int startIndex,int n,int k){
if(path.size()==k){
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex;i<=n-(k-path.size()-1);i++){
path.add(i);
backtracking(i+1,n,k);
path.remove(path.size()-1);
}
}
}
216. 组合总和 III
题目链接
https://leetcode.cn/problems/combination-sum-iii/description/
解题思路
//剪枝方式1 if(targetSum>n){ return; } //剪枝方式2 if(targetSum>n){ path.remove(path.size()-1); targetSum-=1; break; }
剪枝方式2 比方式1 更优,减少了当前层向后循环,只不过提前结束了,要记得回溯函数 这一定要记得。
code
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(0,1,k,n);
return res;
}
public void backtracking(int targetSum,int startIndex,int k,int n){
// if(targetSum>n){
// return;
// }
if(path.size()==k){
if(targetSum==n){
res.add(new ArrayList<>(path));
}
return;
}
for(int i=startIndex;i<=9-(k-path.size())+1;i++){
path.add(i);
targetSum+=i;
if(targetSum>n){
path.remove(path.size()-1);
targetSum-=1;
break;
}
backtracking(targetSum,i+1,k,n);
path.remove(path.size()-1);
targetSum-=i;
}
}
}
17. 电话号码的字母组合
题目链接
https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/
解题思路
画图,每次都是取一个数字的完整字符数组,字符串的长度就是树的深度,每次取字符串下一个的索引字符数组
code
class Solution {
List<String> res=new ArrayList<>();
StringBuilder path=new StringBuilder();
private static final Map<Character,char[]> map=new HashMap<>();
static{
map.put('2',"abc".toCharArray());
map.put('3',"def".toCharArray());
map.put('4',"ghi".toCharArray());
map.put('5',"jkl".toCharArray());
map.put('6',"mno".toCharArray());
map.put('7',"pqrs".toCharArray());
map.put('8',"tuv".toCharArray());
map.put('9',"wxyz".toCharArray());
};
public List<String> letterCombinations(String digits) {
if(digits==null || "".equals(digits)){
return res;
}
backtracking(digits.length(),digits,0);
return res;
}
public void backtracking(int k,String digits,int index){
if(k==path.length()){
res.add(path.toString());
return;
}
char[] charArray=map.get(digits.charAt(index));
for(char c: charArray){
path.append(c);
backtracking(k,digits,index+1);
path.deleteCharAt(path.length()-1);
}
}
}