[学习报告]《LeetCode零基础指南》(第九讲) 简单递归

本文介绍了递归的基本概念,通过解析LeetCode中涉及递归的题目,如阶乘尾随零、数字操作、完全二叉树节点数、路径计数等,展示了如何运用递归解决问题。文章强调理解递归结束条件和结果的重要性,同时也提到了递归与迭代的关系。此外,还探讨了二叉搜索树的范围和、整数替换、二叉树深度等经典问题的递归解法。
摘要由CSDN通过智能技术生成

一,递归的简单介绍

  递归在刚学习编程,我是一碰到递归就头疼的,不过随着后面的不断练习也逐渐不是那么惧怕他了.那么什么是递归呢,我们经常说要理解递归你要先理解递归 ,那么究竟什么是递归呢?简单的来说就是一个函数调用他自己。但是递归的难点不在于此,而是我们不要去尝试理解递归的具体执行过程,这并不是说不能而是对于过程复杂的递归要细究他的执行过程会变得十分十分的复杂。我们需要注意的是递归结束的条件和在某种情况下他是不是符合我们想要的结果就行,比如利用递归求取某个数的阶乘或者第N个斐波那契数就是这个道理,我们不需要去细究他的过程只要知道这个函数给我们返回的是什么结果就够了.

二,做题记录

1.172. 阶乘后的零

原题链接

给定一个整数 n ,返回 n! 结果中尾随零的数量。
提示 n! = n * (n - 1) * (n - 2) * … * 3 * 2 * 1
示例 1:
输入:n = 3
输出:0
解释:3! = 6 ,不含尾随 0
示例 2:
输入:n = 5
输出:1
解释:5! = 120 ,有一个尾随 0
示例 3:
输入:n = 0
输出:0
0 <= n <= 10^4

首先我们想到的肯定是吧n的阶乘求出来然后统计他的尾随0,但是再看数据肯定是不行了。于是我们来看某个数的阶乘:
n ! = n ∗ ( n − 1 ) ∗ ( n − 2 ) ∗ ( n − 3 ) ∗ . . . . . . ∗ 1 n!= n * (n-1 ) *( n-2) * (n-3) *......*1 n!=n(n1)(n2)(n3)......1
那么什么时候在他的末尾会出现零?当然是其内含有10这个因子的时候,也就是我们要找出在其阶乘表达式的数字中找出有多少个能组成10的个数,接下来再继续分解,吧10分解为 2 ∗ 5 2*5 25,我们之后要做的就是找出这些数字中有多少个能组成10的2*5,同时又注意到一点,由于这里面2作为因子的个数一定大于5作为因子的个数的,所以我们只需要统计5的个数即可。
但是问题又来了,例如25 125 625 这些5的幂次方数该如何统计?总不能分别统计这些数的个数吧,所以我们这时候就要用到递归了,对于N,我们每次统计N/5的个数再利用递归去求(N/5)中含有5的个数,直到他<5;

class Solution {
public:
    int get_cnt(int n)
    {
        if(n<5){
            return 0;
        }
        return n/5+get_cnt(n/5);
    }
    int trailingZeroes(int n) {
        return get_cnt(n);
    }
};

忽略掉C++代码这里跟C语言其实没有区别主要看其中的主体就够了.
在这里插入图片描述

2.1342. 将数字变成 0 的操作次数

原题链接

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。
示例 1:
输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。
示例 2:
输入:num = 8
输出:4
解释:
步骤 1) 8 是偶数,除以 2 得到 4 。
步骤 2) 4 是偶数,除以 2 得到 2 。
步骤 3) 2 是偶数,除以 2 得到 1 。
步骤 4) 1 是奇数,减 1 得到 0 。
示例 3:
输入:num = 123
输出:12
提示:
0 <= num <= 10^6

按照题意来就好,对于N如果是奇数减去1,偶数除以2,递归终点是得到0;

int get_cnt(int num){
    if(num==0){
        return 0;
    }
    if(num&1){
        return get_cnt(num-1)+1;
    }else return get_cnt(num/2)+1;
}
int numberOfSteps(int num){
    return get_cnt(num);
}

在这里插入图片描述

3.222. 完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1

这是一道很经典的树形dp了,所谓的树形dp就是对于一棵二叉树来说,我要得到某个结果我需要他左树的信息以及他右树的信息,,那么他的左右子树同理,利用树形dp处理这类问题会使得问题十分简单。
具体的过程呢,我们定义一个 g e t c n t ( ) getcnt() getcnt()函数是得到以某个结点为根的子树的节点数,递归结束的重点是当前结点为叶子结点,然后分别向他的左树,右树要信息,再加上自己的哪一个就是答案。这样一直递归下去就行。

int countNodes(struct TreeNode* root)
{
    if(root==NULL)
    return 0;
    return 1+countNodes(root->left)+countNodes(root->right);
}

在这里插入图片描述

4.LCP 44. 开幕式焰火

原题链接

「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。
给定一棵二叉树 root 代表焰火,节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。
示例1:
输入:root = [1,3,2,1,null,2]
输出:3
解释:焰火中有 3 个不同的颜色,值分别为 1、2、3
示例 2:
输入:root = [3,3,3]
输出:1
解释:焰火中仅出现 1 个颜色,值为 3
提示:
1 <= 节点个数 <= 1000
1 <= Node.val <= 1000

这道题就十分简单了,求取二叉树的某一种遍历序列最后统计即可.这里就利用递归来进行遍历了(因为太懒了,迭代遍历需要用到栈或者队列这里略过吧)

bool vis[1005];
void prevOrder(struct TreeNode* root)
{
   if(root)
   {
       vis[root->val]=true;
       prevOrder(root->left);
       prevOrder(root->right);
   }
}
int numColor(struct TreeNode* root){
    memset(vis,0,sizeof(vis));
    prevOrder(root);
    int ans=0;
    for(int i=1;i<=1000;i++){
        ans+=vis[i];
    }
    return ans;
}

在这里插入图片描述
那次失败的原因是爆栈了,所以将vis数组定义为了全局变量

5.397. 整数替换

原题链接
给定一个正整数 n ,你可以做如下操作:

如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
返回 n 变为 1 所需的 最小替换次数 。
示例 1:
输入:n = 8
输出:3
解释:8 -> 4 -> 2 -> 1
示例 2:
输入:n = 7
输出:4
解释:7 -> 8 -> 4 -> 2 -> 1
或 7 -> 6 -> 3 -> 2 -> 1
示例 3:
输入:n = 4
输出:2

先介绍一种最简单的方法,就是枚举所有情况取较小值。
如果n是偶数,肯定执行n/2毫无疑问,当n为奇数时不管是+1还是-1都会使他变为偶数,那么我们不妨把这两步连到一起。

int min(int a,int b){
    return a<b?a:b;
}
int get_cnt(int n)
{
    if(n==1){
        return 0;
    }
    if(n&1){
        return 2+min(get_cnt(n/2),get_cnt(n/2+1));
        //注意不能直接使N+1或者N-1,这样可能造成数据溢出
        //由于c语言的整形除法会自动向下取整所以我们直接使N/2,得到(n-1)/2的值,其+1得到(n+1/2的值
    }
    return 1+get_cnt(n/2);
}
int integerReplacement(int n){
    return get_cnt(n);
}

在这里插入图片描述
再介绍一种利用位运算的方法:
我们注意到偶数时n/2是使n的二进制整体右移一位,奇数时n-1就是把n的二进制位中最低位的1去掉,n+1就是吧n末尾连续的1全部清除再将其之前的第一个0变为1;而我们要做的就是用最少的操作将n的二进制中的1清除到只剩一个再将其移位到末位。特别的对于3的情况我们直接减一

int get_cnt(unsigned int n){//避免溢出将n定义为unsigned int
    if(n==1){
        return 0;
    }
    if(n&1){
        if(n!=3&&((n>>1)&1)){
            return get_cnt(n+1)+1;//如果n不是3并且n第二位也为1,那么一定比减一能够消除的1多执行+1操作
        }else return get_cnt(n-1)+1;
    }
    return get_cnt(n>>1)+1;
}
int integerReplacement(int n){
    return get_cnt(n);
}

在这里插入图片描述

6.938. 二叉搜索树的范围和

原题链接

给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。
示例 1:
输入:root = [10,5,15,3,7,null,18], low = 7, high = 15
输出:32
示例 2:
输入:root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
输出:23
提示:
树中节点数目在范围 [1, 2 * 104] 内
1 <= Node.val <= 10^5
1 <= low <= high <= 10^5
所有 Node.val 互不相同

我们先了解一下什么叫搜索二叉树,它指的是对与一个结点来说,他的左子树的值都小于他,右子树的值都大于他,我们只需要利用这个特性再对二叉树遍历一遍即可。

int prevOrder(struct TreeNode * root,int l,int r ){
    if(root==NULL){
        return 0;
    }
    if(root->val<l){
        return prevOrder(root->right,l,r);//如果root->val小于左边界说明要到右树上寻找
    }
    if(root->val>r){
        return prevOrder(root->left,l,r);//如果root->val大于有边界说明要到左树上寻找
    }
    return root->val+prevOrder(root->left,l,r)+prevOrder(root->right,l,r);//root->val在范围内,记入结果.
}

int rangeSumBST(struct TreeNode* root, int low, int high){
    return prevOrder(root,low,high);
}

在这里插入图片描述

7.剑指 Offer 55 - I. 二叉树的深度

8.104. 二叉树的最大深度

原题链接

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。

这个仍然是树形DP的入门题,分别向左右子树要深度,再加上自己的深度就是这个子树的最大深度。

int max(int a,int b){
    return a>b?a:b;
}
int maxDepth(struct TreeNode* root)
{
    if(root==NULL)
    {
        return 0;
    }
    return (int)max(maxDepth(root->left)+1,maxDepth(root->right)+1);
}

在这里插入图片描述

9.翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目范围在 [0, 100] 内
-100 <= Node.val <= 100

仍然是十分简单的问题,仔细观察不难发现就是把每个结点的左右子树对换位置即可。

struct TreeNode* invertTree(struct TreeNode* root){
    if(root==NULL){
        return NULL;
    }
    struct TreeNode * tmp=root->left;
    root->left=root->right;
    root->right=tmp;
    invertTree(root->left);
    invertTree(root->right);
    return root;
}

在这里插入图片描述

10剑指 Offer II 110. 所有路径

11.797. 所有可能的路径

原题链接

给定一个有 n 个节点的有向无环图,用二维数组 graph 表示,请找到所有从 0 到 n-1 的路径并输出(不要求按顺序)。
graph 的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些结点(译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a ),若为空,就是没有下一个节点了。
示例 1:
输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
示例 2:
输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
示例 3:
输入:graph = [[1],[]]
输出:[[0,1]]
示例 4:
输入:graph = [[1,2,3],[2],[3],[]]
输出:[[0,1,2,3],[0,2,3],[0,3]]
示例 5
输入:graph = [[1,3],[2],[3],[]]
输出:[[0,1,2,3],[0,3]]

这相当于搜索的模板题了,在这里就换成C++了,否则操作那么多指针太麻烦了。要从0到N-1找到它的所有路,我们搜索每个路径即可。

class Solution {
public:
    void dfs(vector<vector<int>> &ans,vector<int>& tmp,vector<vector<int>>& graph,int now,int target){
        if(now==target){//如果现在到达了target则结束当此搜索并且将tmp加入ans
            ans.push_back(tmp);
            return ;
        }
        for(int i=0;i<graph[now].size();i++){//搜索当前节点的所有路径
            tmp.push_back(graph[now][i]);//先放入当前结点的一条边
            dfs(ans,tmp,graph,graph[now][i],target);//从下一条变开始搜索
            tmp.pop_back();
        }
    }
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        vector<vector<int>> ans;
        vector<int> tmp;
        tmp.push_back(0);//先把0加入tmp
        int target=graph.size()-1;//目标点
        dfs(ans,tmp,graph,0,target);//开始搜索
        return ans;
    }
};

在这里插入图片描述

三,本日收获

本文的题目均来自博主英雄哪里出来的专栏LeetCode零基础指南:《LeetCode零基础指南》(第九讲) 简单递归,想看详细思路的可以自己查看.今天不少题的质量都不错,做起来也很舒服,今天主要是对于一些问题用简单的递归来解决,当然所有的递归都可以改成迭代不过要借助某些方法。另外最后两道题用到了DFS,这也是一个十分重要的算法,要多加练习.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值