回溯法小结(持续更新)

回溯法小结(持续更新)


最近在刷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的时候,我们把他分为两种情况。

  1. 小于0:说明减过头了这时候应该退回上一层节点,即撤销选择,尝试减去别的元素(这里可以进行优化,即剪枝操作我们后面再说)。
  2. 等于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;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值