【算法笔记】组合总和

本文探讨了一个组合问题,即给定一个无重复元素的数组和目标数,找出所有使数字和为目标数的组合。通过分析,确定这是一个回溯问题,并详细解释了解决此类问题的三个步骤:回溯函数的终止条件、单层递归过程和确定递归函数的参数及返回值。同时,介绍了剪枝操作以提高算法效率,强调在有序数组中利用剪枝提前结束无效的搜索路径。
摘要由CSDN通过智能技术生成

组合总和问题

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

问题分析

本问题是一个组合然后求和的问题,本质上就是一个组合问题,找到总和等于目标值的组合,可判断为回溯问题,所有回溯问题都可以抽象为树结构,是在一棵树上的深度优先遍历,那么在这里我们先画出一个简单的树结构,再进一分析问题。
在这里插入图片描述
然后进行我们回溯的三个步骤:

  • 1.回溯函数的终止条件
    到达树的叶子节点就是终止条件,从图中我们可以看出当数的和等于target或者大于target时就到达了叶子节点,这里我们使用一个sum来保存数的和,一维数组path来保存子集,二维数组result来保存总的结果集,所以当sum>=target时就终止。
    得到终止时的代码为:

    //终止本次递归,不做任何操作
    if(sum>target){
       return;
    }
    //保存结果
    if(sum==target){
       result.add(new ArrayList<Integer>(path));
       return;
    }
    
  • 2.单层递归的过程
    一般来说这里有三个基本步骤:
    (1)for循环控制树的横向遍历
    (2)递归控制纵向遍历
    (3)最后进行回溯

在这里插入图片描述

我们再来看看这个树形结构,通过for循环从左到右遍历,然后通过递归函数不断调用自己来进行纵向遍历,当遇到叶子节点时就返回;由于我们选择的元素可以重复,所以每一层的递归都是从该元素本身开始,在这里需要一个标志告诉下一个递归从本身开始。
代码为:

//通过startIndex通知递归的下次开始原始为自己
for(int i=startIndex;i<candidates.lengt;i++){
     path.add(candidates[i]);   //保存节点值
     sum+=candidates[i];   //节点值做加
     backTracking(candidates,target,sum,i);  //递归
     sum-=candidates[i];       //回溯
     path.remove(path.size()-1);  //回溯
}
  • 3.确定递归函数的参数和返回值
    (1)参数:通过上面的分析,确定基本需要的参数有:数组 candidates 、目标数 target、sum(保存目前元素和)、startIndex(下一次递归元素的起始位置)
    (2)返回值:void

整体代码

public class CombinationSum {


    List<Integer> path=new ArrayList<>();   //子集
    List<List<Integer>> result=new ArrayList<>();  //结果集

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
       backTracking1(candidates,target,0,0);
       return result;
    }

    public void backTracking1(int[] candidates,int target,int sum,int startIndex){
        //终止本次递归,不做任何操作
        if(sum>target){
            return;
        }
        //保存结果
        if(sum==target){
            result.add(new ArrayList<Integer>(path));
            return;
        }
        //通过startIndex通知递归的下次开始原始为自己
        for(int i=startIndex;sum+candidates[i]<=target&&i<candidates.length;i++){
            path.add(candidates[i]);  //保存节点值
            sum+=candidates[i];       //节点值做加
            backTracking1(candidates,target,sum,i); //递归
            sum-=candidates[i];                 //回溯
            path.remove(path.size()-1);   //回溯
        }
    }

}


剪枝操作

回溯法唯一提高效率的操作,顾名思义就是减掉一些没有必要的操作。
从图中我们可以看出很多情况的sum都大于了target,比如说是否可以在2+2再加3之前就判断后面的数加上总数会大于target,直接终止操作,把剩余的部分减掉呢?
在这里插入图片描述
我们先分析一下,如果第一个选2,第二个也选2,那么接下来可以选择2、3、4,但是选择3和4的时候总和就会大于target,这时就可以在for循环时增加一个判断条件,直接结束3后面的操作,也就是当sum+candidates[i]>target时结束本轮循环。
注意:这里的例子数组恰好是有序的,题目并没有说candidates数组为有序,如果为{2,3,4,1},我们从3开始就终止了,可是1却是可行的路径,所以我们要使用这个终止条件的前提是给candidates排好序。
实现剪枝的修改代码为:

//传入candidates之前,先给它排好序
Arrays.sort(candidates);

//for循环增加sum+candidates[i]<=target条件
for(int i=startIndex;sum+candidates[i]<=target&&i<candidates.length;i++){
.......
}

//因为这里的for循环已经排除了大于target的可能,所以可以删除之前的sum>target终止
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值