回溯算法理论基础
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯法通常使用递归来实现,在递归过程中不断尝试各种可能的解决方案,如果发现当前的解决方案不可行,就回溯到上一步,换一种方案继续尝试。
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
1、回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合(不强调元素顺序)
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式(强调元素顺序)
- 棋盘问题:N皇后,解数独等等
2、回溯法解决的问题都可以抽象为树形结构(N叉树),所有回溯法的问题都可以抽象为树形结构!
3、因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
4、回溯法的模板
1 void backtracking(参数) { 2 if (终止条件) { 3 存放结果; 4 return; 5 } 6 7 for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 8 处理节点; 9 backtracking(路径,选择列表); // 递归 10 回溯,撤销处理结果 11 } 12 }
77.组合
题目链接:77. 组合 - 力扣(LeetCode)
思路
暴力法:嵌套for循环,但题目中k是不确定的,有多少个k就得嵌套多少个for循环,很明显该方法不可用。
回溯法:首先将问题解决方法抽象成一个树形结构。
具体步骤如下:
- 参数和返回值,返回值一般为void,参数为n(宽度),k(深度),以及startIndex(用来记录下次一递归开始的搜索位置)
- 终止条件,path.size() == k path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,我们就可以将 path 添加到 reslut 的集合当中
- 回溯搜索遍历过程
- 处理节点:将 i 添加到path当中
- 回溯 path.remove(path.size()-1);
代码
1 class Solution { 2 List<Integer> path=new ArrayList<>(); 3 List<List<Integer>> reslut=new ArrayList<>(); 4 public List<List<Integer>> combine(int n, int k) { 5 backTracking(n,k,1); 6 return reslut; 7 } 8 private void backTracking(int n,int k,int startIndex){ 9 //终止条件 10 if(path.size()==k){ 11 reslut.add(new ArrayList<>(path)); 12 return; 13 } 14 //回溯搜索遍历过程 15 for(int i=startIndex;i<=n;i++){ 16 //处理结点 17 path.add(i); 18 //递归 19 backTracking(n,k,i+1); 20 //回溯 21 path.remove(path.size()-1); 22 } 23 } 24 }
减枝优化
优化过程如下:
-
已经选择的元素个数: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。
代码如下
1 class Solution { 2 List<List<Integer>> result = new ArrayList<>(); 3 LinkedList<Integer> path = new LinkedList<>(); 4 public List<List<Integer>> combine(int n, int k) { 5 combineHelper(n, k, 1); 6 return result; 7 } 8 9 /** 10 * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex 11 * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。 12 */ 13 private void combineHelper(int n, int k, int startIndex){ 14 // 终止条件 15 if (path.size() == k){ 16 result.add(new ArrayList<>(path)); 17 return; 18 } 19 // 回溯搜索遍历过程(这里运用了剪枝) 20 for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){ 21 // 处理节点 22 path.add(i); 23 // 递归 24 combineHelper(n, k, i + 1); 25 // 回溯 26 path.removeLast(); 27 } 28 } 29 }
标签:int,Day24,随想录,startIndex,算法,回溯,集合,path,size From: https://www.cnblogs.com/xpp3/p/17152816.html