力扣题解——77. 组合(回溯)

什么是回溯法

在做这道题之前,我们需要对回溯法有一个基本的了解。
我们不妨先思考一下此题的解题思路,题目如下:
在这里插入图片描述
题目意思很简单,找出符合题意的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);
        }
    }
}

总结

组合问题是比较基础的回溯问题,想要解决回溯问题,我们要做好以下几个步骤:

  • 画好解决问题的树状图
  • 想好解决问题过程中变量的设计问题
  • 根据回溯三部曲的顺序,思路,分块写出代码

回溯法和递归一样,开始学习的时候会觉得有些绕,所以一定要多加练习,逐渐体会回溯的过程,理清代码的组成结构,一定会有所进步!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值