递归和回溯1

目录

一:递归+回溯

双递归解法 

二: 回溯

先复习vector遍历 

 回溯法本质就是一个暴力解法

三:利用回溯法解决排列问题


一:递归+回溯

题目:给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于targetSum的路径的数目。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的

 解题需要明确:

  • 因为结点值可以是负数,所以该节点只要不是叶子结点,即使值相等也要递归。
  • 因为必须父->孩子,不可以出现孩子->兄弟,所以会用到回溯
class Solution {
private:
    //注意这里存储前缀和的测试用例数据大,最好改成long
    /*全局变量*/
    map<long,int> record;//record[前缀和:presum]=出现频次
    /*私有函数实现*/
    int findAsPresum(TreeNode* root, int targetSum,long presum){
         if(root==NULL) return 0;
         //计算前缀和
         presum+=root->val;
         //定义返回结果
         int res=0;
         //计算以当前结点为根符合条件的路径
         if(record.find(presum-targetSum)!=record.end())//找到了
              res+=record[presum-targetSum];
         //递归前需要加上访问过的结点presum
         record[presum]++;
         //递归
         res+=findAsPresum(root->left,targetSum,presum);
         res+=findAsPresum(root->right,targetSum,presum);
         //执行该语句时说明这是叶子结点---路径终点
         //需要回溯,因为不可以跨路径
         record[presum]--;
         return res;
    }
public:
    int pathSum(TreeNode* root, int targetSum) {
        //初始化私有变量record
        record[0]=1;//保证起点
        return findAsPresum(root,targetSum,0);//开始没有结点
    }
};

 题目思路:

  1. 累计每一条路径二叉树的结点前缀和,然后在哈希表中存储,每次遇到新结点,去查找表找有没有key值等于:加上该节点后的前缀和-目标值。如果查找表有,就找到了路径。
  2. 然后更新查找表,递归地找路径的下一个节点,累计路径和。
  3. 注意题目的路径要求自上而下,如果该节点是叶子结点,需要不断回退,查找表要不断删去包含了该节点的前缀值。

这里:

目标值是不变的,可以设为私有变量,也可以视为传参,每次赋相同值。这里的哈希表map和前缀和需要更新,必须作为新的函数参数,当然也可以设为私有变量。

class Solution {
private:
    int target;
    /*私有函数实现*/
    int findAsPresum(TreeNode* root,long presum,map<long,int>& record){
         if(root==NULL) return 0;
         //计算前缀和
         presum+=root->val;
         //定义返回结果
         int res=0;
         //计算以当前结点为根符合条件的路径
         if(record.find(presum-target)!=record.end())//找到了
              res+=record[presum-target];
         //递归前需要加上访问过的结点presum
         record[presum]++;
         //递归
         res+=findAsPresum(root->left,presum,record);
         res+=findAsPresum(root->right,presum,record);
         //执行该语句时说明这是叶子结点---路径终点
         //需要回溯,因为不可以跨路径
         record[presum]--;
         return res;
    }
public:
    int pathSum(TreeNode* root, int targetSum) {
        map<long,int> record;
        record[0]=1;//保证起点
        target=targetSum;
        return findAsPresum(root,0,record);//开始没有结点
    }
};

传哈希表---注意加&,因为它在运行过程中不断改变。

双递归解法 

class Solution {
private:
    int nodeinPath(TreeNode* node, long target){
          if(node==NULL) return 0;
          int res=0;
          if(node->val==target) res+=1;//找到符合条件,不管是不是叶子结点,路径++
          //寻找是否成立
          res+=nodeinPath(node->left,target-node->val);
          res+=nodeinPath(node->right,target-node->val);
          return res;
    }
public:
    int pathSum(TreeNode* root, int targetSum) {
          if(root==NULL) return 0;
          int res=0;
          res+=nodeinPath(root,targetSum);//以自己为根
          //改变传入的根,主函数递归
          res+=pathSum(root->left,targetSum);
          res+=pathSum(root->right,targetSum);
          return res;
    }
};

nodeinPath():

负责找值,每次以传入结点为根,递归的找:结点值==目标-已遍历结点和

  • if(node->val == num) res += 1;--------找到的依据
  • res +=nodeinPath(node->left , num - node->val);---递归减值
  • res += nodeinPath(node->right , num - node->val);----递归减值

pathSum():

负责改变根,因为路径可以从孩子开始。

  • findPath(root, sum) ;-----以自己为根
  • pathSum(root->left , sum) + pathSum(root->right , sum);----改变根,递归主函数

二: 回溯

先复习vector遍历 

 注意:

  • string的初始化:vector<string> 变量名{列表}
  • 拷贝与赋值:=或者()
  •  auto与类型::iterator一个意思

树形问题

 一些边界:

  1. 字符串的合法性
  2. 空字符串的处理---直接返回
  3. 多个解的顺序----这里无要求

class Solution {
private:
    /*两个私有变量----数字代表值以及返回结果*/
    const string letterMap[10]={
        " ",//0
        "",//1
        "abc",//2
        "def",//3
        "ghi",//4
        "jkl",//5
        "mno",//6
        "pqrs",//7
        "tuv",//8
        "wxyz"//9
    };
    vector<string> res;
    /*递归调用函数,完成函数功能*/
    void findOneLetter(const string& digits,int index,const string& s){
        //该函数,每次针对digits[index]处的字符的所有可能记录到s
        //s保存的是:digits[0...index-1]的所有结果
        if(index==digits.size()){
            //遍历结束,存储结果
            res.push_back(s);
            return;
        }
        char c=digits[index];
        string numToString=letterMap[c-'0'];//把char字符转换成数组下标int
        for(int i=0;i<numToString.size();i++){
        findOneLetter(digits,index+1,s+numToString[i]);
        }
    }
public:
    vector<string> letterCombinations(string digits) {
         if(digits=="") return res;
         findOneLetter(digits,0,"");
         return res;
    }
};

findOneLetter(const string& digits,int index,string s) 

几点注意

char转换成int,直接字符减去48或者‘0’

        string numToString=letterMap[digits[index]-'0'];

        for(int i=0;i<numToString.size();i++)

        findOneLetter(digits,index+1,s+numToString[i]);

             一次循环递归便找到了所有可能

 回溯法本质就是一个暴力解法

三:利用回溯法解决排列问题


class Solution {
/*回溯过程:
全排列(数组)=从数组取出一个元素+全排列(不包含该元素的数组)*/
private:
    vector<vector<int>> res;
    vector<bool> used;
    void genPermute(const vector<int>& nums,int index,vector<int>& p){
        if(index==nums.size()){
            res.push_back(p);
            return;
        }
        //核心---递归逻辑
        for(int i=0;i<nums.size();i++){
            //判断该元素是否已经在p中
            if(!used[i]){
                //把nums[i]添加到p中
                p.push_back(nums[i]);
                used[i]=true;
                //递归
                genPermute(nums,index+1,p);
                //回溯
                p.pop_back();
                used[i]=false;
            }
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
       if(nums.size()==0) return res;
       used=vector<bool>(nums.size(),false);
       vector<int> p;
       genPermute(nums,0,p);
       return res;
    }
};

#include<iostream>
#include<vector>
#include<string>
using namespace std;
/*
解题思路:全排列(数组)=随便选出一个元素+全排列(数组-选出的元素)
注意: 每次递归结束后可得到一个符合要求的结果,然后不断回溯得到所有结果
*/
class Solution {
/*全排列(nums)=随便选一个数a+全排列(nums没有a)*/
private:
    //私有变量:res为返回结果,visited表示元素是否访问
    vector<vector<int>> res;
    vector<bool> visited;
    //递归求解
    void getPermute(vector<int>& nums,int index,vector<int>& m){
        //index:记录当前处理的第几个元素,是递归结束的标志
        //m记录一个可能的结果
        if(index==nums.size()){
            //记录结果m+return
            res.push_back(m);
            return;
        }
        for(int i=0;i<nums.size();i++){
            //保证了长度
            if(!visited[i]){//没访问
               /*当前元素加入m中,标记已访问*/
               m.push_back(nums[i]);
               visited[i]=true;
               /*递归其他可能,满足m长度*/
               getPermute(nums,index+1,m);
               /*递归结果,回溯,回溯要记得还原标记*/
               m.pop_back();
               visited[i]=false;
            }
        }
        return;
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
      if(nums.size()==0) return res;
      //初始化变量
      visited=vector<bool>(nums.size(),false);
      vector<int> p;//生成一个可能的排列,初始化不存在
      getPermute(nums,0,p);
      return res;
    }
};
int main(){
    vector<int> aa={1,2,3};
    vector<vector<int>> bb=Solution().permute(aa);
    for(int i=0;i<bb.size();i++){
        cout<<"[";
        for(int j=0;j<bb[i].size();j++){
            cout<<bb[i][j]<<",";
        }
        cout<<"] ";
    }
    return 0;
}

看程序输出语句:

 

 注意----如果在回退过程中,可选的结果已经执行了,递归或回溯有记忆。

其实index这个变量可以不引入

class Solution {
private:
    vector<vector<int>> res;//记录返回结果
    vector<bool> visited;//标记是否访问
    //递归函数
    void genPermute(vector<int>& nums,vector<int>& p){
        if(p.size()==nums.size()){
            res.push_back(p);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(!visited[i]){
                p.push_back(nums[i]);
                visited[i]=true;
                genPermute(nums,p);
                p.pop_back();
                visited[i]=false;
            }
        }
        return;
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if(nums.size()==0) return res;
        //对私有变量初始化
        visited=vector<bool>(nums.size(),false);
        vector<int> p;//记录单个正确结果
        genPermute(nums,p);
        return res;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值