递归

简介: 树和链表都是天然的递归结构

递归三要素

树和链表都是天然的递归结构

1. 明确函数的作用

由我们自己定义

2. 寻找递归终止条件

递归就是函数自己调用自己,当参数为什么时,我们能够直接知道函数的结果,这时递归终止,将函数值进行返回

3. 找出函数的等价关系式(等价操作步骤)

我们不断缩小参数的范围,缩小之后要通过辅助的变量或操作使原函数的结果不变

侧重于函数的功能,忽略实现步骤

  1. 辅助的变量(缩小参数范围+变量):适用于数字计算之类的题目f(n)=n*f(n-1)
  2. 操作(缩小参数范围+操作):适用于有节点的数据结构(链表,树)reverseList(head)等价于reverseList(head.next)+改变一下1、2两个节点的指向

3.1 经验之谈

当我们在具有节点的数据结构中使用递归时,递归返回值有两类

  1. 我们要返回找到的节点
    直接return 递归函数(缩小范围的参数);因为我们函数的目的是为了找到某个节点,所以getNode(node.left, key)与getNode(node, key)是等价的,最终结果都是同一个节点

   //返回以node为根的二分搜索树中,键为key的节点

   //返回的是当前节点

   privateNodegetNode(Nodenode,Kkey){

       //如果根为空,返回null

       if (node==null)

           returnnull;

       //key<根节点的key,去左子树中寻找,缩小参数范围

       if (key.compareTo(node.key)<0)

           returngetNode(node.left, key);

       //key>根节点的key

       if (node.key.compareTo(key)>0)

           returngetNode(node.right, key);

       //key==node.kay

       elsereturnnode;

   }

  1. 我们要返回增删之后新的二分搜索树的根
    remove(node.left)显然不与remove(node)等价,因此我们要进行一些操作,使其等价
    node.left=remove(node.left);
    return node;

//移除以node为根的二分搜索树中最小的节点

//返回移除后新的二分搜索树的根

privateNoderemoveMin(Nodenode){

       //递归终止条件

       //当前节点的左子树为空,那么就为最小节点。

       //可能其还有右子树,我们提取出来他的右子树,其右节点理所当然成为新的二分搜索树的根

       if (node.left==null){

           //保存被删节点的右子树

           NoderightNode=node.right;

           //将被删除的节点从当前二叉树中脱离关系

           node.right=null;

           size--;

           returnrightNode;

       }

       //removeMin(node)等价于removeList(node.left)+改变node.left指向新的右子树的根

       node.left=removeMin(node.left);

       returnnode;

   }

递归的优化

1. 记忆化搜索(避免重复计算)

2. 动态规划(自底向上)

递归、回溯、dfs(深度优先搜索)、动态规划

  1. 递归:自我调用(作为编程的一种实现方式),dfs、回溯、动态规划都可以用递归来实现,也可以用非递归实现:
  2. 回溯:一种算法思想,把问题分步解决,在每一步都试验多有的可能,当发现已经找到一种方式或者当前这种方式不可能是结果,就退回上一步尝试其他可能(回溯可以用于所有用穷举法可以解决的问题)
  3. dfs:回溯用于树的时候就是dfs。几乎所有可以用回溯解决的问题都可以表示为树,如果显式的使用了树,那么就叫dfs(两者都可以剪枝算法)
  4. 动态规划:拥有最优子结构

回溯

解决一个回溯问题,实际就是一个决策树(在每个节点上都在做决策)的遍历过程。只需要思考三个问题:

  1. 路径:已经做出的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:到达决策树底层,无法再做选择的条件(路径==选择列表长度)

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

#include<iostream>;

#include<vector>;

usingnamespacestd;

classSolution {

private:

   vector<vector<int>>res;

   vector<int>e;

   voidrecursion(vector<int>&e, vector<int>&nums) {

       if (e.size() ==nums.size()) {

           res.push_back(e);

           return;

       }

       for (inti=0; i<nums.size(); i++) {

           // if (count(e.begin(), e.end(), nums[i])!=0)

           //      continue;

           if(find(e.begin(),e.end(),nums[i])!=e.end())

               continue;

           e.push_back(nums[i]);

           recursion(e, nums);

           e.pop_back();

       }

       return;

   }

public:

   vector<vector<int>>permute(vector<int>&nums) {

       res.clear();

       e.clear();

       recursion(e, nums);

       returnres;

   }

};

17. 电话号码的字母组合

#include<iostream>;

#include<vector>;

usingnamespacestd;

classSolution {

private:

   vector<string>res;

   //路径

   strings;

   //字母与坐标的映射

   conststringletterMap[10] = {

       "",

       "",

       "abc",

       "def",

       "ghi",

       "jkl",

       "mno",

       "pqrs",

       "tuv",

       "wxyz"

   };

   voidfindCombinations(stringdigits,intindex) {

       //路径长度==数字字符串长度

       if (s.size() ==digits.size()) {

           res.push_back(s);

           return;

       }

       charc=digits[index];//数字

       stringletters=letterMap[c-'0'];//数字对应的字符串选择列表(选择列表)

       for (intj=0; j<letters.size(); j++) {

           s.push_back(letters[j]);

           findCombinations(digits, index+1);

           s.pop_back();

       }

       return ;

   }

public:

   vector<string>letterCombinations(stringdigits) {

       s="";

       res.clear();

       if (digits=="")

           returnres;

       findCombinations(digits, 0);

       returnres;

   }

};

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

classSolution {

public:

   //返回以root为根的二叉树的最大深度

   intmaxDepth(TreeNode*root) {

       //终止条件

       if(root==NULL)

           return0;

       //方法1

       // //左子树的最大深度

       // int depth1 =maxDepth(root->left);

       // //右子树的最大深度

       // int depth2 =maxDepth(root->right);

       // return depth1 > depth2 ? depth1+1 : depth2+1;

       //方法2

       //return maxDepth(root->left)<maxDepth(root->right)?maxDepth(root->right)+1:maxDepth(root->left)+1;

       

       //方法3

       returnmax(maxDepth(root->left),maxDepth(root->right))+1;

   }

};

这里方法2会运行超时,我也不知道为什么

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

如果左子树为零,那么直接返回右子树即可!

classSolution {

public:

   intminDepth(TreeNode*root) {

       if(root==NULL)

           return0;

       intdepth1=minDepth(root->left);

       intdepth2=minDepth(root->right);

       if(depth1==0)

           returndepth2+1;

       elseif(depth2==0)

           returndepth1+1;

       else

           returnmin(depth1,depth2)+1;

   }

};

226. 翻转二叉树

翻转一棵二叉树。

以根节点为轴,翻转二叉树

classSolution {

public:

   TreeNode*invertTree(TreeNode*root) {

       if(root==NULL)

           returnNULL;

       //翻转左子树

       invertTree(root->left);

       //翻转右子树

       invertTree(root->right);

       //根节点进行交换

       swap(root->left,root->right);

       returnroot;

   }

};

100. 相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

classSolution {

public:

   boolisSameTree(TreeNode*p, TreeNode*q) {

       //先判断是否为空,再判断值,避免空指针异常

       if(p==NULL&&q==NULL) returntrue;

       if(p==NULL||q==NULL) returnfalse;

       if(p->val!=q->val) returnfalse;

       returnisSameTree(p->left,q->left)&&isSameTree(p->right,q->right);

   }

};

112. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

叶子节点 是指没有子节点的节点。

class Solution {

public:

   bool hasPathSum(TreeNode* root, int targetSum) {

       if(root==NULL)

           return false;

       //左子树和右子树都为空才为叶子节点

       if(root->left==NULL&&root->right==NULL)

           return root->val==targetSum;

       if(hasPathSum(root->left,targetSum-root->val))

           return true;

       if(hasPathSum(root->right,targetSum-root->val))

           return true;

       return false;

   }

};

收获

  1. 先判断节点是否为空,避免空指针异常

257. 二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

/**

* Definition for a binary tree node.

* struct TreeNode {

*     int val;

*     TreeNode *left;

*     TreeNode *right;

*     TreeNode() : val(0), left(nullptr), right(nullptr) {}

*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}

*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}

* };

*/

class Solution {

public:

   vector<string> binaryTreePaths(TreeNode* root) {

       vector<string> str;

       if(root==NULL)//传入节点为空,什么都不做

           return str;

       if(root->left==NULL&&root->right==NULL){

           str.push_back(to_string(root->val));

           return str;

       }

       vector<string> leftStr=binaryTreePaths(root->left);

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

           str.push_back(to_string(root->val)+"->"+leftStr[i]);

       vector<string> rightStr=binaryTreePaths(root->right);

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

           str.push_back(to_string(root->val)+"->"+rightStr[i]);

       

       return str;

   }

};

收获

  1. to_string:将int转换为string
目录
相关文章
|
5月前
|
存储
【递归知识+练习】
【递归知识+练习】
42 0
|
8月前
递归
递归
34 0
|
9月前
|
存储 算法 C++
递归的应用
递归的应用
|
10月前
|
Java 数据安全/隐私保护 决策智能
字符串全排列(递归)
字符串全排列,递归的应用
112 0
|
10月前
|
算法 Python
递归的使用
递归的使用
32 0
|
算法 索引
第 6 章 递归
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。
52 0
|
存储 Serverless 开发者
递归的理解与实现
递归的理解与实现
递归的理解与实现
|
机器学习/深度学习
简单的了解一下递归
在编程中,递归大家肯定都不陌生了吧,今天我们来总结总结有关于递归的东西。
|
机器学习/深度学习 算法
『递归』整数划分
根据n和m的关系,考虑一下几种情况: (一)当n==1时,无论m的值为多少 ,只有一种划分,即{1} (二)当m==1 时,无论n的值为多少,只有一种划分,即1个n,{n} 。 (三)当n==m时,根据划分中是否包含n,可以分为以下两种情况: (1)划分中包含n的情况,只有一个,即 {n} (2)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分,即q(n,n-1)。 因此q(n,m)=1+q(n,n-1) (四)当n<m时,由于划分中不可能出现负数,因此就相当于q(n,n) (五)当n>m 时,根据划分中是否包含最大值m,可以分为以下两种情况:
130 0
|
机器学习/深度学习 算法 物联网
一文看懂递归
一文看懂递归