leetcode 40组合总和II -回溯

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

分析:本题是经典的回溯+递归的做法,即对于每个数都有选择和不选择两个选择,但是如果只是这样选择,答案会出现重复 比如[2,2],target = 2,【2】的答案会出现两次。即第一次选取第一个2,第二次选取第二个2.
为了解决重复的问题,我们可以开创一个列表,里面存储的是一位数组,数组下标为0存储元素的值,下标为1存储该数出现的次数。
假设不同的数共有m个,因此我们可以遍历,假设一个数的出现次数是i,那么我们就可以考虑对该数选择0次,1次…i次的情况,分别进行递归,这样就不会出现重复的情况,当当前位置选择到了m个时,说明已经递归结束,没有多余的数在可以选,当rest <0时也说明此时无法凑到rest,也可以进行退出。

这样一来,我们就可以不重复地枚举所有的组合了。

我们还可以进行什么优化(剪枝)呢?一种比较常用的优化方法是,我们将 freq 根据数从小到大排序,这样我们在递归时会先选择小的数,再选择大的数。这样做的好处是,当我们递归到dfs(pos,rest) 时,如果 freq[pos][0] 已经大于 rest,那么后面还没有递归到的数也都大于 rest,这就说明不可能再选择若干个和为rest 的数放入列表了。此时,我们就可以直接回溯。

class Solution {

    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    //用来记录每个待选元素的值和出现的次数
    List<int[] > freq = new ArrayList<int[]>();

    //当前的序列
    List<Integer> seq = new ArrayList<Integer>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        
        //先对candidates进行排序
        Arrays.sort(candidates);

        for(int num : candidates){
            int size = freq.size();
            if(freq.isEmpty() || freq.get(size - 1)[0]!= num){
                
                //这里要记住这种写法
                freq.add(new int[]{num,1});
            }else{
                freq.get(size - 1)[1]++;
            }
        }

        //开始回溯
        dfs(0,target);
        return ans;
    }

    public void dfs(int pos,int rest){
        if(rest == 0){
            ans.add(new ArrayList<Integer>(seq));
        }else if(rest < 0 || pos == freq.size()){ //这里需要思考 当pos == freq.size()时,为什么要退出,因为总共有这么多数
            return ;
        }else{

            //开始遍历 对于每个数若出现次数为n,则可以选0次,1次...选n次的做法
            //对于选0次的时候
            dfs(pos + 1,rest);

            //这是对于每个数最多选择的次数,第一个表示的是 如果超过这个次数 其和就会大于rest 没必要,所以剪枝
            int most = Math.min(rest / freq.get(pos)[0],freq.get(pos)[1]);
            for(int i = 1;i <= most;i++){
                seq.add(freq.get(pos)[0]);

                //这里是选择 i个第pos个数
                dfs(pos + 1,rest - i*freq.get(pos)[0]);
                //执行完上一个dfs 推出的时候
                //一定会执行到这里 继续下一个i的选择
            }

            //这里要回溯
            //将之前添加到seq中的most个数值删除掉,到这里肯定说明前面most个都已经添加到seq中
            for(int i = 1;i <= most;i++){
                seq.remove(seq.size() - 1);
            }
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值