回溯算法-组合
Posted 执章学长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回溯算法-组合相关的知识,希望对你有一定的参考价值。
原题
https://leetcode.cn/problems/combinations/
思路
回溯算法实际上就是深度优先算法的一种。也就是说,当我们在使用一个深度优先算法(一般用递归)的时候,有时候操作完一个步骤需要回退到上一步,这就叫回溯。
那么,既然回溯就是dfs的一种,我们前面也讲过dfs的步骤,最重要的一步就是一次操作。首先,这道题的是想要求组合。那么假设k=2时,是不是表示我们从数字中选取两次,记录所有的结果,那么其实所谓的一次操作其实就是选取一个数字,并且,我们知道组合【1,2】和组合【2,1】是一样的(看做一个),所以说,当我们选择了【1,2】之后,我们在第一次选择【2】的时候,第二次就不能选择【1】了。那么我们既然已经知道了单次操作是啥,是不是只需要递归就好了。(回溯等后面来讲)。
分析的过程正如上面这张图,因为k等于2,也就是说我们每个小集合需要选取两次。第一次操作时,我们可以选择任何一个数【1 2 3 4】,第二次操作时,我们要去除掉前面已经选过的,防止【1 2】和【2 1】这种情况;也要去除掉上一次(k=1)时选取的,即防止集合变成【1 1】这种。
好的,以上这些内容和我前面写的深度优选算法(递归)是一样的(其实回溯算法一般来说,本质也是在递归的基础上实现的)。
那么我们为什么要使用回溯呢?
这里考虑一个问题。首先,我们因为要存储子集对吧,所以我们一开始要定义一个大的集合存储所有的子集,其次要定义一个子集,用于存储一次选择,例如【1,2】。那么我们思考一下上面递归的过程。这里一定要结合上面的图,才能体会到回溯的精髓。
- 一开始我们的子集是空的。【】
- 第一次调用递归函数,选择一个1,此时的临时子集为【1】
- 接着,再次调用递归函数(即第二次选择数字),选择一个2,此时的集合为【1 2】
- 好了,此时我们选择了了两次(因为k=2,所以已经选完了),我们将子集装入大集合中。
- 大集合拥有了一个子集【1 2】,那么此时函数执行完毕,回到上一次,也就是2 3 4给你选。
- 那么我们这时候如果没有回溯,是不是临时子集目前还是【1 2】?答案是是的。
- 这里很多新人就会想那我能不能把临时子集清空呢?其实完全没必要,因为临时子集前面的1是我们已经选了的,我们回退到第二层,此时的1是有意义的,那么我们只需要回退掉2就好了(也就是回退掉一次操作,一般都是递归一次就得回退一次)。
- 那么当我们把【1 2】回退到【1】的时候,我们就可以从 2 3 4 中选择,由于2已经选过了,也就是说接下来是选择3,子集变成【1 3】.
- 上面的过程实际上就是深度优先算法(递归),只是我们在每次递归的时候都需要一次【回退】,也就是【回溯】。
- 而回溯在代码中的体现也非常的接地气,无非就是我们在递归前添加了元素,递归后将最后一个元素删除而已。操作不难,主要是理解思想。
代码
class Solution
List<List<Integer>> all = new ArrayList<>();
List<Integer> part = new ArrayList<>();
public List<List<Integer>> combine(int n, int k)
tCombine(n,k,0,new ArrayList());
return all;
public void tCombine(int n,int k,int index,List<Integer> part)
if(k == 0)
all.add(new ArrayList(part));
if(index == n)
return;
for(int i = index;i < n;i++)
part.add(i+1);
tCombine(n,k-1,i+1,part);
part.remove(part.size()-1);
补充
这道题我们说了【回溯】,但是这道题的还可以进一步优化,即通过【剪枝】来减少递归次数。后面会讲。
以上是关于回溯算法-组合的主要内容,如果未能解决你的问题,请参考以下文章