定义什么的就不多说了,上期都有,还有这期涉及到上期的知识,最好结合上期看。
上期指路:递归回溯题目总结(第一期)
例题
组合总和
给定一个无重复元素的正整数数组和一个目标正整数,找出数组中所有可以使数字和为目标正整数的组合(数组中的一个数可以被重复使用)
①选择一个实例[2,3,5]与8画图
这个图没有画完,但是基本清楚大致,流程。
②确定结束条件
定义一个sum,记录一次深入递归遍历的和,如果等于目标数,则结束,加入结果集。
//条件判断
if(sum==target){
res.push_back(path);
return;
}
③确定选择列表
我们明显可以发现每个子树的根节点下面一层的选择列表均为[2,3,5],因为可以重复选。也就是说这个start不会随着深度的增加而+1,而是始终保持不变。
for(int i=start;i<candidates.size();i++)
④剪枝去重
①中图我没有画完是有原因的,因为这题可以重复使用,所以如果不进行一些适当的剪枝处理,就会一直遍历无用的树枝。剪枝条件也简单。只要sum大于目标数,就直接跳过此次循环。
//减枝
if(sum>target){
continue;
}
⑤⑥⑦选择、递归、撤销
由于可以重复选择,递归时i不变(即递归传过去的start不变)
//选择
path.push_back(candidates[i]);
//由于可以重复选择,这里递归就不需要i+1
dfs(candidates,path,res,target,sum+candidates[i],i);
//撤销
path.pop_back();
完整代码:
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> path;
vector<vector<int>> res;
dfs(candidates,path,res,target,0,0);
return res;
}
void dfs(vector<int>& candidates,vector<int>& path, vector<vector<int>>& res,int target,int sum,int start){
//条件判断
if(sum==target){
res.push_back(path);
return;
}
for(int i=start;i<candidates.size();i++){
//减枝
if(sum>target){
continue;
}
//选择
path.push_back(candidates[i]);
//递归
//由于可以重复选择,这里递归就不需要i+1
dfs(candidates,path,res,target,sum+candidates[i],i);
//撤销
path.pop_back();
}
}
};
组合总和 II
给定一个数组(可以有重复数字)和一个目标数,找出数组中所有可以使数字和为目标数的组合。
每个数字只能使用一次
①选择实例[1,1,2]和3画图
图未画全。
②确定结束条件与上面一题一样
③确定选择列表
由于这题是不能重复选取一个数的,所以选择列表左区间start应该是上一层选择的节点的下一个节点。
for(int i=start;i<candidates.size();i++)
④剪枝,很明显sum>target的剪枝条件必不可少,并且由于不能重复使用,所以同一层中不能使用相同的数,和上一期的第二题一样。当然这样的前提也是需要将数组排序,不然相同数不一定挨在一起
//排序,为剪枝做好准备
sort(candidates.begin(),candidates.end());
if(i>0 && candidates[i]==candidates[i-1])
但是我们又发现一个问题,虽然同一层中不能重复使用,但是由于数组中可能有相同数字导致不同层可以使用相同的数字(也就是说如果题目给的数组中有重复数字,最终结果有可能有重复数字的)而上面的条件将所有重复情况都否定了,所以不对。
所以需要给每位数字设置一个是否使用的标志位
vector<bool> used(candidates.size(),false)
就是说我们需要剪枝的是同一层重复的,因为不在同一层,一个深度路线上的话,那么前一个会被设置为使用过,在同一层就是由于回溯对于当前其上一个没有使用过。
也就是说used[i-1]=true代表不在同一层,在深度路径,我们要剪枝的是同一层的,所以最终条件需要在基础上加上一个used[i-1]==false
//剪枝
if(i>0 && candidates[i]==candidates[i-1] && !used[i-1]){
continue;
}
⑤⑥⑦选择、递归、撤销
由于一个数只能使用一次,所以递归时的i需要i+1(即传过去的start要是上次选择节点的下一个节点)
//选择
path.push_back(candidates[i]);
used[i]=true;
//递归
dfs(candidates,path,res,target,sum+candidates[i],i+1,used);
//撤销
path.pop_back();
used[i]=false;
完整代码:
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> path;
vector<vector<int>> res;
vector<bool> used(candidates.size(),false);
//排序,为剪枝做好准备
sort(candidates.begin(),candidates.end());
dfs(candidates,path,res,target,0,0,used);
return res;
}
void dfs(vector<int>& candidates,vector<int>& path, vector<vector<int>>& res,int target,int sum,int start,vector<bool>& used){
//条件判断
if(sum==target){
res.push_back(path);
return;
}
for(int i=start;i<candidates.size();i++){
if(!used[i]){
//剪枝
if(sum>target){
continue;
}
//剪枝
if(i>0 && candidates[i]==candidates[i-1] && !used[i-1]){
continue;
}
//选择
path.push_back(candidates[i]);
used[i]=true;
//递归
dfs(candidates,path,res,target,sum+candidates[i],i+1,used);
//撤销
path.pop_back();
used[i]=false;
}
}
}
};