Combination Sum求和为给定值的所有序列的解题思路

因为数字可以重复,毫无疑问,这道题要用回溯来解。写出代码不难如下:

class Solution {
public:
    vector<vector<int> > rec_combinationSum(vector<int> &candidates, int target) {
        vector<vector<int>> res;
        if(target==0){
        	res.push_back(vector<int>());
        	return res;
        }
        
        int tar = target;
        for(auto c:candidates){
            target -=c;
            if(target<0){target +=c;continue;}
            auto vv = rec_combinationSum(candidates,target);
            for(auto vec:vv){
                if(accumulate(vec.begin(),vec.end(),c) == tar) {
                    vec.push_back(c);
                    sort(vec.begin(),vec.end());
                    res.push_back(vec);
                }
            }
            target +=c;
        }
        return res;
    }

    vector<vector<int> > combinationSum(vector<int> &candidates, int target){
	    vector<vector<int>> res;
	    if(candidates.size()==0) return res;
	    sort(candidates.begin(),candidates.end());
	    res = rec_combinationSum(candidates,target);
	    sort(res.begin(),res.end());
        auto it=unique(res.begin(),res.end());
        res.resize(distance(res.begin(),it));
    
        return res;
    }
};

但是,有一个问题就是运行速度很慢,在Leetcode168个测试用例上出现了TLE,这是回溯需要解决的问题。如果先不考虑剪枝,是不是因为代码中大量的排序导致变慢呢?每次计算得到一个结果都要排序,而且还有不少重复的。如果用set或者unordered_set来存储是否可以搞定呢?要为unordered_set<vector<int>>写一个hash函数类,因为vector<int>不是基本基本类型,代码如下:

class MyHash
{
public:
    size_t operator()(const vector<int> &v) const
    {
        size_t x = 0;
        for (auto &i : v)
            x ^= std::hash<int>()(i);
        return x;
    }
};

class Solution {
public:
    unordered_set<vector<int>,MyHash > rec_combinationSum(vector<int> &candidates, int target) {
        unordered_set<vector<int>,MyHash> res;
        if(target==0){
        	res.insert(vector<int>());
        	return res;
        }        
        int tar = target;
        for(auto c:candidates){
            target -=c;
            if(target<0){target +=c;continue;}
            auto vv = rec_combinationSum(candidates,target);
            for(auto vec:vv){
                if(accumulate(vec.begin(),vec.end(),c) == tar) {
                    vec.push_back(c);
                    sort(vec.begin(),vec.end());
                    res.insert(vec);
                }
            }
            target +=c;
        }
        return res;
    }

    vector<vector<int> > combinationSum(vector<int> &candidates, int target){
	    unordered_set<vector<int>,MyHash> tmp;
	    vector<vector<int> > res;
	    if(candidates.size()==0) return res;
	    sort(candidates.begin(),candidates.end());
	    tmp = rec_combinationSum(candidates,target);
	    
	    for(auto vec:tmp)
	    res.push_back(vec);
    
        return res;
    }
};

结果发现仍然是TLE,这里虽然省去了排序,去重,理论上插入还是线性,但仍然不行。看来数据结构上的优化无法帮助解决本TLE问题。
下面只能考虑剪枝了。怎么剪?这个问题有什么特点?
正常情况下,当选定一个数字之后,下面仍然要把每个数字都再考虑一遍;有没有一种方法,某个数字的情况考虑完之后就不再考虑该数字出现的情况?存在不存在?答案是存在,那就是要对数列进行排序,排序之后,我们发现如果某个数字考虑过,则包含该数字的所有情况,包括重复的情况都在回溯的过程中考虑了。所以之后,就不必再考虑该种情况了,此处可剪枝。

class Solution {
public:
    vector<vector<int>> rec_combinationSum(vector<int> &candidates, int target,int idx) {
        vector<vector<int>> res;
        if(target==0){
        	res.push_back(vector<int>());
        	return res;
        }
        
        int tar = target;
        for(int i=idx;i<candidates.size();++i){
            target -=candidates[i];
            if(target<0){target +=candidates[i];continue;}
            auto vv = rec_combinationSum(candidates,target,i);
            for(auto vec:vv){
                if(accumulate(vec.begin(),vec.end(),candidates[i]) == tar) {
                    vec.push_back(candidates[i]);
                    sort(vec.begin(),vec.end());
                    res.push_back(vec);
                }
            }
            target +=candidates[i];
        }
        return res;
    }

    vector<vector<int> > combinationSum(vector<int> &candidates, int target){
	    vector<vector<int> > res;
	    if(candidates.size()==0) return res;
	    sort(candidates.begin(),candidates.end());
	    res = rec_combinationSum(candidates,target,0);
    
        return res;
    }
};

增加了一个参数,该参数保存当前访问的数值,考虑完之后不再考虑之前的所有元素而是从下一个开始,达到了剪枝的目的。速度大概是108ms,仍然不佳。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值