题目描述
给一个不含重复元素数组和一个数target
,求在可重复抽取数组元素的情况下,所有和为target
的情况,输出要从小到大排序。
知识点
回溯DFS+剪枝
运行结果
码前思考
- 我一开始想要用动态规划中的完全背包+充满背包做的,但是很久没刷题了,想不起来该怎么做了。。。
- 我是看了网上的题解才懂得。
- 就是简单地暴力dfs,初始
sum
为0,然后暴力枚举每个数字,为了不含重复的情况,所以遍历后的数字就不能在后面考虑了。 - 同时,可以进行一个小小的剪枝,对数组进行排序,如果
当前数字+sum
超过了target
,那么后面的数字也肯定超过target
,所以直接放弃。
代码实现
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
//dfs函数表示以当前idx为基准的范围内是否能够得到结果
void dfs(int idx,int sum,vector<int>& candidates, int target){
if(sum == target){
res.push_back(path);
return;
}
for(int i=idx;i<candidates.size()&&sum+candidates[i]<=target;i++){
path.push_back(candidates[i]);
sum+=candidates[i];
dfs(i,sum,candidates,target);
path.pop_back();
sum-=candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
//首先进行排序,方便剪枝
sort(candidates.begin(),candidates.end());
//使用加法进行dfs
dfs(0,0,candidates,target);
return res;
}
};
//下面这份代码没有将判断超过target放到循环条件中,所以会很长,而且也没有剥离等于target
的情况。。。dfs函数一般在一开始都会有递归边界的,这是需要注意的。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
//dfs函数表示以当前idx为基准的范围内是否能够得到结果
void dfs(int idx,int sum,vector<int>& candidates, int target){
for(int i=idx;i<candidates.size();i++){
path.push_back(candidates[i]);
sum+=candidates[i];
if(sum==target){
res.push_back(path);
path.pop_back();
sum-=candidates[i];
}else if(sum<target){
dfs(i,sum,candidates,target);
path.pop_back();
sum-=candidates[i];
}else{
path.pop_back();
sum-=candidates[i];
break;//直接break,后面的肯定不行的~
}
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
//首先进行排序,方便剪枝
sort(candidates.begin(),candidates.end());
//使用加法进行dfs
dfs(0,0,candidates,target);
return res;
}
};
码后反思
- 太久没做题了,今天的手感异常生疏,感觉回到了解放前了。。。
- 突然夏令营没有机试了,会改成手撕代码吗,害,这太难了。。。感觉leetcode题目更加适合面试题,pat上的题太长了,所以刷刷leetcode热题100吧。。。
- 关于动态规划背包的解法,可以参考如下:
类似于动态规划求满足条件的组合数的转移方程dp[i]+=dp[i-x]
这里的转移方程为path[i]
拼接上path[i-x]
的所有再加上x
的组合路径class Solution { public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<vector<int>>> path(target+1); path[0]={{}}; for(auto x : candidates){ for(int i=x;i<=target;++i){ for(auto v : path[i-x]){ v.push_back(x); path[i].push_back(v); } } } return path[target]; } };
算法执行的结果不太好。
算法的思想就是跟背包差不多,表示的是最后选择的是第n件物品的情况下,有哪些可能情况,有点绕,不管了。。。掌握回溯就行 - dfs还是难呀,根本不知道会不会超时,反正dfs一定是剪枝的,这是毋庸置疑的。。。
- 既然没有机试了,那么就每天刷刷leetcode,或者看看微信公众号,保持思考吧。。。
参考题解
- 回溯算法 + 剪枝
- 图解算法微信公众号
二刷代码
看了提示,知道要用回溯来解题,所以得出了下面的代码:
//为了得到不重复的结果,需要强制只能使用自己后面的元素,一种典型的dfs暴力方法
//相似的题目还有之前那个求最短的
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
int len = candidates.size();
dfs(candidates,target,0);
return res;
}
void dfs(vector<int>& candidates,int target,int idx){
if(target==0){//不需要求和了
res.push_back(path);
}else if(target<0){
return;//小于0肯定不行
}else{
for(int i=idx;i<candidates.size();i++){
path.push_back(candidates[i]);
dfs(candidates,target-candidates[i],i);
path.pop_back();
}
}
}
};