回溯法小结(持续更新)
最近在刷leetcode,遇到了一些问题,主要是关于回溯法,这里就当作一个笔记来进行总结,主要以题驱动,若有不对的地方,请多多见谅。(主要使用的语言是c++)
首先,回溯法是什么?回溯法本质上是一个遍历过程,其实现方式主要是基于递归。回溯法是一个暴力搜索过程,只不过在这个给过程中我们可以适当进行优化操作,这个优化过程也就是剪枝过程。下面先给出回溯法的基本框架(模板):
void dfs(...) {//递归函数
if(){//中止条件
return ;
}
for(...){//
xxx.emplace_back(XX);//选择
dfs(...);//递归
xxx.pop_back();//撤销选择
}
}
从这个模板可以看到,我们主要关注的就是中止条件和选择过程,其中选择过程是核心,下面基于具体题目来分析如何使用回溯法解决问题。
一、组合总和问题
下面是leetcode第39题
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
示例
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
为了方便理解这里贴出leetcode题解中的树状图:
从树状图可知,他以target为根节点,每一次选择用数组内的一个元素去减,可以注意到元素可重复使用。(这里我们可以看到数组是升序的,实际情况可能给出的不是升序,所以我们最好先将数组进行升序排序。)而这个递归的中止条件即减到小于等于0的时候,我们把他分为两种情况。
- 小于0:说明减过头了这时候应该退回上一层节点,即撤销选择,尝试减去别的元素(这里可以进行优化,即剪枝操作我们后面再说)。
- 等于0:说明找到了一条和为target得组合,这时候我们可以把这条路径存储起来。
上述可知我们可以写出中止条件的代码:
if(target<0){
return ;//中止
}
if(target==0){
ans.emplace_back(combine);//将路径存储
return ;//中止
}
下面我们来分析选择过程,由树状图可知,每一层都会减去数组的每个数字,所以遍历是遍历数组每一个元素。那么我们可以写出下面代码:
for(int i=site;i<candidates.size();i++){//
combine.emplace_back(candidates[i]);//选一个入栈(做选择)
dfs(candidates,target-candidates[i],ans,combine,i);//这里是i,因为可以重复选择一个元素
combine.pop_back();//退栈(撤销选择)
}
对于这个选择过程,我们没有进行优化,所以下面可以尝试对这个选择过程进行优化(剪枝):
for(int i=site;i<candidates.size();i++){//
if(target-candidates[i]<0){//剪枝
break;
}
combine.emplace_back(candidates[i]);//选一个入栈(做选择)
dfs(candidates,target-candidates[i],ans,combine,i);//这里是i,因为可以重复选择一个元素
combine.pop_back();//退栈(撤销选择)
}
由于我们将数据先进行了升序排序,所以如果当前用当前元素减已经小于0,那么后面的元素也就不用遍历了,用break跳出循环即可,基于上述过程,完整代码如下:
class Solution {
public:
//回溯法 ans:目标数组(二维) combine:暂存一条路径(一维) indx:当前指针位置(用于剪枝,去重:例如,223 232 322 去掉232 322这种)
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine,int indx) {
if(target<0){
return ;
}
if(target==0){
ans.emplace_back(combine);
return ;
}
for(int i=indx;i<candidates.size();i++){//遍历
if(target-candidates[i]<0){//剪枝
break;
}
combine.emplace_back(candidates[i]);
dfs(candidates,target-candidates[i],ans,combine,i);//因为可重复使用一个数字 所以是i 而不是i+1
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
sort(candidates.begin(),candidates.end());
dfs(candidates,target,ans,combine,0);
return ans;
}
};