39. Combination Sum

1题目理解

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

It is guaranteed that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

输入:int数组 int[] candidates 和 int数值 target
规则:数组中的元素可以不限次数的选择和使用,让选择的数字和等于target
输出:输出所有的组合,不能重复。

2 回溯分析

例如当前在处理candidates[index]元素,可以选择使用这个元素,时候之后还可以继续使用;也可以选择不使用这个元素,不使用的话,就继续考察下一个元素。
优化的地方是:先将数组排序,排序之后当candidates[index]>target就可以不用继续递归下去。
差点忘记了,做回溯题目最重要的是画回溯树。
在这里插入图片描述

例如 candidates=[2,3,6,7],target=7
回溯树每个节点的状态dfs(index,target),表示当前处理第index个元素,目标值是target。
当处理第0个元素的时候,可以选择元素2,也可以不选择。
选择元素2,进入状态dfs(0,5),组合=[2]。
不选择元素2,进入状态dfs(1,7),组合=[]。
这里有个奇怪的想法,我不想选择第1个2,我想选择第2个2可以吗?也就是说不选择元素2,进入状态dfs(0,7),我还想选择第二次的那个2。这里是不可以。因为这样的话,是会重复的。

class Solution {
    private int[] candidates;
    private List<List<Integer>> answer;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        this.candidates = candidates;
        answer = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();
        dfs(0,target,list);
        return answer;
    }
    
    private void dfs(int index,int target,List<Integer> list){
        if(target==0){
            answer.add(new ArrayList<Integer>(list));
        }else if(index>= candidates.length){
            return;
        }else{
            if(target<candidates[index]){
                return;
            }
            
            list.add(candidates[index]);
            dfs(index,target-candidates[index],list);
            list.remove(list.size()-1);
            
            dfs(index+1,target,list);
        }
    }
}

时间复杂度,一个比较松的上届是 O ( n ∗ 2 n ) O(n*2^n) O(n2n)。n是数组长度。每个位置有选和不选两种选择所以有 2 n 2^n 2n

3 40. Combination Sum II

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target.

Each number in candidates may only be used once in the combination.

Note: The solution set must not contain duplicate combinations.
Constraints:

1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30

3.1 延续39解题思路

39 题数组中每个元素不重复,但是能无限池使用;40题虽然每个元素只能使用一次,但是元素可能重复。也就是说两者的区别是39对于一个元素使用次数不限,40,对于一个元素使用次数有限。那就看看每个元素能使用多少次吧。
因为 1 <= candidates[i] <= 50 ,所以可以设计一个length=51的数组,计算每个元素还能使用的次数。用count数组计算每个元素能使用的次数。
假设candidates = [10,1,2,7,6,1,5], target = 8。那么1可以使用2次,2可以使用一次,用(1,2),(2,1),(5,1),(6,1)(7,1)(10,1)这样的序列对表示。
先画个递归树。
在这里插入图片描述

这里状态dfs(num,target),num表示当前正在处理的元素。注意:这里与39不同,39是index,表示下标。这里是num,表示元素本身。
初始状态dfs(1,8)。1是candidates中的最小元素。
选择1,计数(1,1),进入状态dfs(1,7)。
不选择1,进入状态dfs(2,8)。

对于处理元素num,
如果选择num,选择之后要看num的计数是否大于0,是就进入dfs(num,target-num),不是的话,需要查看下一个计数大于0的数。进入dfs(nextNum,target-num)。

如果不选择num,那就需要查看下一个计数大于0的数。进入dfs(nextNum)。

class Solution {
    private int[] count = new int[51];
    private List<List<Integer>> answer;
    private int[] candidates;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        for(int num : candidates){
            count[num]++;
        }
        this.candidates = candidates;
        answer = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();
        dfs(candidates[0],target,list);
        return answer;
    }
    
    private void dfs(int num,int target,List<Integer> list){
        if(target==0){
            answer.add(new ArrayList<Integer>(list));
        }else if(num>= count.length){
            return;
        }else{
            if(target<num){
                return;
            }
           
            list.add(num);
            count[num]--;
            if(count[num]>0){
                dfs(num,target-num,list);
            }else{
                int nextNum = num+1;
                while(nextNum<count.length && count[nextNum]==0){
                    nextNum++;
                }
                dfs(nextNum,target-num,list);
            }
            
            list.remove(list.size()-1);
            count[num]++;
            
            int nextNum = num+1;
             while(nextNum<count.length && count[nextNum]==0){
                    nextNum++;
             }
            dfs(nextNum,target,list);
        }
    }
}

3.2 新思路

单看数组[1,1,5,6,7,10]。按照正常的递归思路。dfs(index,target)。
如果选择nums[index],则进入dfs(index+1,target-nums[index])状态。
如果不选择,则进入dfs(index+1,target)。
这里因为有重复数据,所以我们观察一下。如果target=7。
选择第0个1,和第3个6,可以得到结果。
选择第1个1,和第3个6,可以得到结果。但是重复了。说明第0个1不选择,那第1个1也不能选择。否则就重复了。这个也和第39有类似。不选择第0个1,选择第1个1是不可以的。

class Solution {
    private List<List<Integer>> answer;
    private int[] candidates;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        
        this.candidates = candidates;
        answer = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();
        dfs(0,target,list);
        return answer;
    }
    
    private void dfs(int index,int target,List<Integer> list){
        if(target==0){
            answer.add(new ArrayList<Integer>(list));
        }else if(index>= candidates.length){
            return;
        }else{
            if(target<candidates[index]){
                return;
            }
            
            list.add(candidates[index]);
            dfs(index+1,target-candidates[index],list);
            list.remove(list.size()-1);
            
            while(index+1<candidates.length && candidates[index+1]==candidates[index]){
                index++;
            }
            dfs(index+1,target,list);
            
        }
    }
}

3.3 递归计数的方式

对于第39题,解法如下。对于第40题也一样。只是计数方式不同。

class Solution {
    private int[] candidates;
    private List<List<Integer>> answer;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        this.candidates = candidates;
        answer = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();
        dfs(0,target,list);
        return answer;
    }
    
    private void dfs(int index,int target,List<Integer> list){
        if(target==0){
            answer.add(new ArrayList<Integer>(list));
        }else if(index>= candidates.length){
            return;
        }else{
            dfs(index+1,target,list);
            int maxCount = target/candidates[index];
            for(int i=1;i<=maxCount;i++){
                list.add(candidates[index]);
                dfs(index+1,target-i*candidates[index],list);
            }
            for(int i=1;i<=maxCount;i++){
                list.remove(list.size()-1);
            }
        }
    }
}

4 216. Combination Sum III

Find all valid combinations of k numbers that sum up to n such that the following conditions are true:

Only numbers 1 through 9 are used.
Each number is used at most once.
Return a list of all possible valid combinations. The list must not contain the same combination twice, and the combinations may be returned in any order.
例子:
Input: k = 3, n = 7
Output: [[1,2,4]]
Explanation:
1 + 2 + 4 = 7
There are no other valid combinations.

说明:1到9每个数字使用一次,每次取k个数,使得k个数之和等于n。

class Solution {
    private List<List<Integer>> answer;
    public List<List<Integer>> combinationSum3(int k, int n) {
        answer = new ArrayList<List<Integer>>();
        dfs(1,k,n,new ArrayList<Integer>());
        return answer;
    }
    
    private void dfs(int num,int k,int n,List<Integer> list){
        if(k==0){
            if(n==0){
                answer.add(new ArrayList<Integer>(list));
            }
            return;
        }
        if(num>9) return;
        if(num>n) return;
        for(int i=num;i<=9;i++){
            list.add(i);
            dfs(i+1,k-1,n-i,list);
            list.remove(list.size()-1);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值