首页 > 编程语言 >代码随想录算法Day24 | 回溯算法理论基础,77.组合

代码随想录算法Day24 | 回溯算法理论基础,77.组合

时间:2023-02-24 19:12:24浏览次数:86  
标签:int Day24 随想录 startIndex 算法 回溯 集合 path size

回溯算法理论基础

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯法通常使用递归来实现,在递归过程中不断尝试各种可能的解决方案,如果发现当前的解决方案不可行,就回溯到上一步,换一种方案继续尝试。

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

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循环,很明显该方法不可用。

回溯法:首先将问题解决方法抽象成一个树形结构。

具体步骤如下:

  1. 参数和返回值,返回值一般为void,参数为n(宽度),k(深度),以及startIndex(用来记录下次一递归开始的搜索位置)
  2. 终止条件,path.size() == k path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,我们就可以将 path 添加到 reslut 的集合当中
  3. 回溯搜索遍历过程
  4. 处理节点:将 i 添加到path当中
  5. 回溯 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 }

减枝优化

 

优化过程如下:

  1. 已经选择的元素个数:path.size();

  2. 还需要的元素个数为: k - path.size();

  3. 在集合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

相关文章