39, 40:Combination Sum 1, 2

39. Combination Sum

这道题就是简单的回溯,需要注意的是为了避免重复结果以及降低时间复杂度,可以先对cans数组进行排序,然后每次回溯的时候从chs数组的最后一个元素在cans数组中的位置开始进行,这样就保证了解序列是升序,从而避免了重复回溯,因此也减少了时间。

class Solution {
public:
    void DFS(int now, int target, vector<int>& chs, vector<int>& cans, int* idx, vector<vector<int>>& result){
        if(now == target){
            result.push_back(chs);
            return;
        }
        int i = (!chs.empty() ? idx[chs.back()] : 0);
        for(; i < cans.size(); i++){
            if(now + cans[i] > target) break;
            chs.push_back(cans[i]);
            DFS(now+cans[i], target, chs, cans, idx, result);
            chs.pop_back();
        } 
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> chs;
        vector<vector<int>> result;
        if(target == 0) return result;
        sort(candidates.begin(), candidates.end());
        int idx[1000];
        for(int i = 0; i < candidates.size(); i++){
            idx[candidates[i]] = i;
        }
        DFS(0, target, chs, candidates, idx, result);
        return result;
    }
};

40. Combination Sum II

本题由于重复数字的存在,比上一题难一些,主要难点是如何避免重复结果。当然可以选择使用set,虽然也能AC,不过那就大大增加时间复杂度了,当然是不能接受的。
为什么会产生重复结果呢?如果我们依旧先对数组进行排序,那么重复数字就是排列在一起的。比如对于 (1 1 1 1...),如果某个解包括4个1,那这个解当然是不会重复的,因为一共只有4个1;如果某个解包含1的个数小于4大于0,比如3个,那么此解一定会重复,因为回溯的会产生如下选择:(1 1 1 0), (0 1 1 1), (1 0 1 1), (1 1 0 1)(1代表选择该位置的解,0代表不选)。所以,产生重复的原因就是某个重复数字的小于其重复个数的组合数是大于1的。
那要如何避免重复呢?我们考虑一下本题中回溯的执行过程,只要到该位置的时候和不超过target,该位置上的数就会被选择的,也就是从该位置上的数开始新的DFS,如果和已超过,则由于已进行过排序,后面位置的数都被该位置的数大,后面也就不用进行了,直接回溯即可。如果现在选择情况是(0 1),即没有选择第一个1,选择了第二个1,从第二个1开始新的DFS,那么很明显选择(1 0)所生成的解与其是相同的,因为都是到了该选第三个数的时候前面选了一个1。再想一下就可以发现该情况其实发生在从第一个1开始的DFS结束之后,而(1 0)这种情况是在从第一个1开始的DFS中已经发生过的,所以(0 1)这种情况所产生的解其实是与之前已经得到的解相重复的。其实对于重复的数字来说,凡是这种没选排在前面的数却选后面的情况所产生的解都是重复的,实质上是从后面的数字开始DFS所产生的解被从前面数字开始DFS所生成的解所包含,这是显而易见的,因为对于重复n次的数字k来说,从第一个k开始DFS是可能选择1~n个k的,而从第二个k开始则只可能选择1~n-1个k(假设和不会超过target),后者所生成的解很明显是包含后者所生成的解的。那么我们只需要在每次决定是否选择该位置的数的时候判断一下左边一位与其相同的数是否被选择即可,如果没被选择,那么由其DFS所产生的解就是重复的,直接跳过该位置即可,这样一来就可以十分简单地去除重复解了。

class Solution {
public:
    void DFS(int now, int p, int target, vector<int>& chs, vector<int>& cans, int* used, vector<vector<int>>& result){
        if(now == target){
            result.push_back(chs);
            return;
        }
        for(int i = p + 1; i < cans.size(); i++){
            if(now + cans[i] > target) return;
            if(i && cans[i] == cans[i-1] && !used[i-1]) continue;
            used[i] = 1; chs.push_back(cans[i]);
            DFS(now+cans[i], i, target, chs, cans, used, result);
            used[i] = 0; chs.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> chs;
        vector<vector<int>> result;
        if(target == 0) return result;
        sort(candidates.begin(), candidates.end());
        int used[candidates.size()] = {0};
        DFS(0, -1, target, chs, candidates, used, result);
        return result;
    }
};

转载于:https://www.cnblogs.com/JingwangLi/p/10494403.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值