什么是回溯法
在做这道题之前,我们需要对回溯法有一个基本的了解。
我们不妨先思考一下此题的解题思路,题目如下:
题目意思很简单,找出符合题意的k个数的组合
那接下来我们来讨论一下解决思路
回溯法的基本思路
当k等于2时很简单,我们自然会想到先确定一个数作为开头,再顺序往后添加该位置后的其他的数。
根据这个思路,我们可以大致把这个过程画成一个树状图:
每个框中的第一行表示当前选取的组合
第二行中的元素表示可以继续选取的元素
最后,最后一层的第一行就是我们要求的最终结果啦
这其实就是最基础是回溯法的应用,抛开回溯法的具体实现,我们先从单层逻辑和递归逻辑去分析
单层逻辑
单层逻辑就是树状图中同一层中在干的事,拿刚刚说的组合问题举例,我们就是在从可选择添加的元素中依次选取一个元素,再去决定是否进入下一层
而我们这里说的下一层,就是我们要实现的递归逻辑
递归逻辑
从树状图中我们可以看出,我们需要重复实现每一层的单层逻辑,而这一过程我们就要用递归进行实现
“回溯”体现在哪里
看了上面的过程,你可能会产生疑问:这看起来似乎就是循环递归啊,为什么又要叫“回溯”呢?
对了,其实在前面我还没有提到解决此题的关键步骤——回溯的过程
一旦我们找到了符合题意目标,将它记录下来以后,接下来要做什么呢?我们不妨再来看看图:
我们需要寻找的组合结果全部在叶子节点中,并且我们只有通过叶子节点的父亲节点,才能找到该叶子节点,也就是说只要通过所有叶子节点的父亲节点,找到所有叶子节点,我们便解决了这一问题
我们再多思考一下,调用函数本身,也就是递归过程找到了结果之一,我们在回的过程自然要清除一下刚刚向存储当前结果的容器中,清楚刚刚添加进去的数据,整个过程就可以称作为回溯
代码的编写
大致思路确定后,我们就可以进行代码的编写了
回溯三部曲
确定好回溯的思路后,我们按照接下来回溯三部曲的思路填充代码就可以成功解决问题
递归函数的返回值以及参数
关于函数返回值:
这题我们直接把符合条件的结果存入res即可,所以函数不用返回任何参数
- nums[]:题目传进的寻找组合的数组
- startIndex:记录本层中开始遍历的位置
- depth:向下寻找的深度(树中所在的层数)
- k:目标深度
我们最终确定的函数传入参数如下:
private void combineHelper(int[] nums,int startIndex,int depth,int k)
回溯函数终止条件
回溯的终止条件很简单,如果寻找深度大于k,我们便不在本层寻找,直接把现在记录下的组合保存即可:
if(depth>k){
res.add(new ArrayList<>(path));
return ;
}
单层搜索的过程
单层搜索过程其实也不难,一个for循环便可以解决,从startIndex开始,到nums.length结束,依次向其中添加即可:
for(int i=startIndex;i<nums.length;i++){
path.add(nums[i]);
combineHelper(nums,i+1,depth,k);
path.remove(path.size()-1);
}
这里我们用path来保存我们当前路径下我们得到的组合
添加元素后不要忘记继续向下层寻找组合(代码:combineHelper(nums,i+1,depth,k);)
注意
path.remove(path.size()-1) 这条语句,这条语句是重点体现回溯法“回”思想的语句,当向下寻找完毕后,回到本层的结点。我们还需要移动到紧挨的下一个结点(也可能没有下一个结点),重复相同的动作,向下寻找组合。
由于我们这里的path设置的是全局变量,单层每个结点只能添加一个元素,在移动到紧挨的下一个结点前,所以我们必须移除掉我们当前结点添加的元素。
整体代码
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
//创建数组
int[] nums=new int [n];
for(int i=0;i<n;i++)
nums[i]=i+1;
int depth=0;
combineHelper(nums,0,depth,k);
return res;
}
private void combineHelper(int[] nums,int startIndex,int depth,int k){
depth++;
if(depth>k){
res.add(new ArrayList<>(path));
return ;
}
for(int i=startIndex;i<nums.length;i++){
path.add(nums[i]);
combineHelper(nums,i+1,depth,k);
path.remove(path.size()-1);
}
}
}
总结
组合问题是比较基础的回溯问题,想要解决回溯问题,我们要做好以下几个步骤:
- 画好解决问题的树状图
- 想好解决问题过程中变量的设计问题
- 根据回溯三部曲的顺序,思路,分块写出代码
回溯法和递归一样,开始学习的时候会觉得有些绕,所以一定要多加练习,逐渐体会回溯的过程,理清代码的组成结构,一定会有所进步!