39. Combination Sum

本文详细解析了一道经典的算法题目——组合总和。通过回溯法结合深度优先搜索(DFS),寻找候选数字集中所有可能的组合,使得这些组合的数字之和等于目标值。文章深入分析了算法的时间和空间复杂度,并提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

QUESTION

Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note
- All numbers (including target) will be positive integers.
- The solution set must not contain duplicate combinations.

For example, given candidate set [2, 3, 6, 7] and target 7,

A solution set is:

[
  [7],
  [2, 2, 3]
]
THOUGHTS

这道题完完全全是自己做出来的,略开心啊。不过还是调试了几次,利用的是回溯法加上dfs,这种做法应该很熟悉了,上课看书是理解不了的,只有多做题才能熟练掌握,不是提倡题海战术,只不过要多做题多思考多总结。给出一列数组,还有一个目标值,求所有和为目标值的集合。其实dfs就是一棵树啊,回溯只不过是借助这种结构进行剪枝。

CODE
public class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if(candidates == null || candidates.length == 0)
            return res;
        List<Integer> temp = new ArrayList<Integer>();
        Arrays.sort(candidates);
        dfs(candidates,target,res,temp,0,0);
        return res;
    }
    public void dfs(int[] nums,int target,List<List<Integer>> res,List<Integer> temp,int sum,int start){
        if(sum == target){
            sum = 0;
            res.add(new ArrayList<Integer>(temp));
            return;
        }
        else if(sum > target){
            sum = 0;
            return;
        }
        for(int i = start;i < nums.length;i++){
            temp.add(nums[i]);
            sum += nums[i];
            dfs(nums,target,res,temp,sum,i);
            sum -= nums[i];
            temp.remove(temp.size() - 1);
            while(i < nums.length - 1 && nums[i] == nums[i + 1])
                i++;
        }
    }
}
RESULT

runtime complexity is O(n^2),space complexity is O(1).

#
SPECIAL ATTENTION
  • 在进行处理之前要先排序,因为可能会出现[3,2,2]的情况,答案给出的是[2,2,3]。
  • 排序之后,也要考虑结果重复的情况,比如candidates[] = {2,2,3},target = 5;那么处出现[2,3],[2,3]俩个结果,最好是在处理的时候跳过这种情况。方法就是如果下一个处理数和本次处理数相同,则跳过。
  • 还要考虑出现的一种情况是,结果中包含[2,2,3],[2,3,2],[3,2,2]为什么我们从小到大排序了还会出现这种情况呢?这是因为我们进行下次递归的时候不是从本节点以后开始的,而是又从0开始的,解决方法是,设置一个参数start,下次进行递归的时候从start开始,如果不允许结果重复,那么就从start + 1开始递归。很尴尬下道题就是这么出的,所以秒解。还需要注意的一点是当开始递归的时候,传递的参数不是start而是变成了i;
二刷

时间复杂度还是不会算,网上看到大神的答案也没看懂。
接下来就是复杂度分析。这道题的复杂度分析起来比较复杂….一刷的时候就没有好好思考。

首先我们假设这个数组candidate长为n, 在其中有m个元素小于target,假设T(n) = find(target),那么我们可以根据程序得到以下分析结果:

DFS Level 0: 我们首次调用辅助函数, 这一层的count = 1
DFS Level 1: 这时候我们进入了辅助函数的for循环,在for循环里我们有一个pruning,当candidate[i] > target的时候,返回,所以这一层我们只对小于target的元素进行下一层DFS,如我们假设的,结果为m
DFS Level 2: 根据代码,我们上一层有一个target -= num,所以这一层的target其实都不一样。
假设我们对candidate中最小的元素min进行分析,这时候新的target1 = target - min,此时我们要继续计算在candidate数组中有多少元素小于新的target1,假设这个数目为m1,则在这一层我们要对小于target1的m1个数组进行下一层的DFS
假如我们考虑其他非min的元素来计算总的复杂度时, 这时候两层总共要进行1 + m(m1 + m2 + m3 + … + mn)次调用。
由于每一层的target和m都在改变,以我的水平比较难计算出一个漂亮的公式,那么我就想办法简化一下:
这里递归的最大深度是d= target / min, 就是最深我们可以到 target / min这么多层DFS
Branching factor b = m, m是candidate数组里小于等于target的distinct元素的个数。 其实这里多算了,每一层的m都在减少,而且是不规则减少
我们利用DFS公式可以算出这里的Time Complexty = O(md), Space Complexity = O(m)
这只是一个worst case scenario, 更精确的计算还需要再花时间。

这是大神的csdn

二刷CODE
public class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        Arrays.sort(candidates);
        List<Integer> temp = new ArrayList<Integer>();
        if(candidates == null || candidates.length == 0)
            return result;
        sumHelper(result,temp,candidates,target,0);
        return result;
    }
    public void sumHelper(List<List<Integer>> res,List<Integer> temp,int[] nums,int target,int index){
        if(target == 0){
            res.add(new ArrayList(temp));
            return;
        }
        if(target < 0)
        //这里之所以是return不是continue是因为数组是经过排序的
        //如果当前的元素大于目标数,就没有必要再继续了,后面的肯定也大于
            return;
        for(int i = index;i < nums.length;i++){
            if(nums[i] > target)
                return;
            //注意这里是i大于index
            if(i > index && nums[i] == nums[i - 1])
                continue;
            temp.add(nums[i]);
            target -= nums[i];
            sumHelper(res,temp,nums,target,i);
            temp.remove(temp.size() - 1);
            target += nums[i];
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值