因为数字可以重复,毫无疑问,这道题要用回溯来解。写出代码不难如下:
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,仍然不佳。