力扣:组合总和

239 阅读3分钟

前言

组合问题,是继排列问题之后的又一个经典回溯问题,可以说和排列问题一样,有着相同的思路,类似的解法,但是仍然有许多细节需要处理,大部分人都吊死在了组合解二叉树上面,下面,我们一起来探索一下吧!

回溯模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }

题目

给你一个 无重复元素 的整数数组candidates和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

示例

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

思路分析

伪代码5

题目意思很明显,找到目标值的元素总和,实际上就是跟排列一样,不断循环candidates数组并且每次取其中一个元素,最终取得可以得到目标数组的元素数组,压入结果数组,得出结果。

我们也利用一个栈track来暂时储存我们的目前找到的元素集合。假如让你们来找,你们会怎么做呢?找出所有元素之间的相互组合,然后一一进行比对吗?很显然这确实可以,但是工作量太大,代码效率太低,不建议这么写。所以我们转变一种思路。

我们采用另外一种方式,每次数组的元素进栈的时候,target就减去相应的进栈元素的值,再拿到下一次循环当中去寻找变化之后的target,这个时候就会出现三种情况:

  • 如果target = 0,那么就表示已经找到了一组结果;
  • 如果target < 0,那么表示当前的元素不够target值,可以进行下一次循环;
  • 如果target > 0,那么表示当前的元素总和已经超过了target值,所以此时就该进行回退操作,继续上述的判断;

这些都还是大体的思想,还有一些细节,比如当target < 0的时候,如果到最后的循环的时候,target的值都小于0,那么就表示没有找到目标值,此时我们也进行回退操作,回退到上一次的循环判断;

我们来看看找到一组结果的细节:

candidates = [2,3,6,7], target = 7,

  • 循环第一次数组,2进栈,3,6,7待定,target = 5
  • 递归,循环第二次数组,同样的情况,此时track当中已经存在两个2了,3,6,7待定,target = 3
  • 循环第三次数组,2进栈,3,6,7待定,target = 1
  • 循环第四次数组,2此时大于1,这个时候加进2就会使得元素总和超过7,所以此时回退;
  • 回退到第三次数组,待定的3进栈,此时target = 3,减去之后变为0,压进结果数组,获得结果[2,2,3]

我们根据这种思想找到了一组结果,剩下的结果数组也是这样。就不一一讲解了。

var combinationSum = function(candidates, target) {
    let len = candidates.length
    let res = []
    const findTarget = (candidates,track,target,index)=>{
        if(target ===0){
           res.push([...track])
           return
        }
        for(let i=index;i<candidates.length;i++){
            let cur = candidates[i]
            if(cur<target){
                track.push(cur)
                findTarget(candidates,h,target-cur,i)
                track.pop(cur)
            }else if(cur>target){
                return
            }else{
                track.push(cur)
                res.push([...h])
                track.pop(cur)
                return
            }
        }
    }
    candidates = candidates.sort((a,b)=>a-b) // 从小到大排序
    findTarget(candidates,[],target,0)
    return res
};

代码分析

我们也是利用回溯目标结果数组,用于返回结果数组,在递归函数当中,当target = 0时,终止操作,但是在处理数组元素上面,我们需要定义一个cur变量来表示每次入栈的元素,并将其与变化之后的target进行比对,然后执行相应的操作,直到target变为0,找到目标数组;没有找到就返回0即可。

总结

回溯的题目仍旧需要多加练习,总结经验,不断的汲取方法,以后遇到这种题目才不会束手无策!

我是小白,我们一起练习力扣!