《LeetCode零基础指南》(第十讲) 简单递归

前言

一入递归深似海,做完题顺带还复习了树的相关知识,一举双得。
最后两题没什么时间做了,下次有时间在做吧

1. 阶乘后的零

172. 阶乘后的零

  • 题目

给定一个整数 n ,返回 n! 结果中尾随零的数量。

提示 n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1

  • 思路分析
  • 这里千万不要头铁算出阶乘的结果,不然你会体验到溢出的恐怖

  • 这种数学类的题目,首先做的是分析题目给出的式子。先考虑什么情况下会产生尾随0?当某个数乘以10的时候。那么是不是将给定的数除以10就可以呢?答案是否定的,因为5的阶乘也是有尾随0的,所以直接除10是不行的。

  • 进一步分析10这个因子可知,它是由2和5组成的,所以我们可以分别统计2和5的因子个数,从而得出结果。

  • 分析到此题目已经可以解出来了,但这还不是最优解,因为观察阶乘的式子我们可以发现,有5的产生就必定会有因子数2的存在,所以2这个因子数是不需要统计的,只需要计算5的个数就行了。

  • 源码实现
int trailingZeroes(int n){
    //当 n = 5时, 1 * 2 * 3 * 4 * 5 = 120; ==> 尾随0 为1
    //当 n = 10时, 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 = 120 * 42 * 72 * 10  = 3628800 ==>尾随0为2

    //观察上述的式子可以推导出:有尾随0的情况一定有2和 5的因子,且有5的因子存在的时候前面一定会有因子2
    //进一步我们可以发现5的因子个数就是尾随0的因子个数

    //由此我们可以通过求出5的因子个数来求出尾随0的个数
    if(n < 5) //小于5的阶乘不存在尾随0,因为尾随0的产生必定是有2和5两个因子产生的
    {
        return 0;
    }
    //每次递归除以5,因为除完一次后,可能得到的因子数里面还有5这个因子数
    return n / 5 + trailingZeroes(n / 5); //求5的因子个数,即尾随0的个数
}

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

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

  • 题目

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

  • 思路分析

思路一:循环实现

  • 使用条件语句判断此时n的数的奇偶性,分别做对应的运算。运算一次操作数加1。

思路二:递归实现

  • 题目是将num变成0,那么就设置0为基准条件
  • 递归函数的参数是由分别以题目给出的两个条件做递归,记住在每次计算后返回值加1统计操作数
  • 源码实现

思路一

int numberOfSteps(int num){
    // //计数器统计步数
    int cnt = 0;

    while(num)
    {
        if(!(num & 1)) //取偶数
        {
            num /= 2;
        }
        else    //奇数减1
        {
            num -= 1;
        }
        cnt++;
    }
    return cnt;
}

思路二

int numberOfSteps(int num){
    if(num == 0)    //0为递归的基准条件
    {
        return 0;
    }
    if(num & 1)
    {
        return numberOfSteps(num - 1) + 1;  //操作一次数值加1
    }
    else
    {
        return numberOfSteps(num / 2) + 1;
    }
}

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

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

  • 题目

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-complete-tree-nodes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 思路分析

树的相关题目,首先考虑的是递归实现。

这里实际上考查的是树的遍历,递归实现的话一般是先遍历所有的左子树,在访问右子树。注意递归的时候加1统计个数

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */


int countNodes(struct TreeNode* root){
    if(root == NULL)    //递归基准条件
    {
        return 0;	//为空时无节点,所以节点数为0
    }
    //根据定义,先遍历左边所有的节点,然后遍历右边的所有节点,为NULL时返回
    /*
    放在递归函数前面实现的是先序遍历
    递归顺序:1->2->4->5->3->6(前序遍历)
        root->val:1
        root->val:2
        root->val:4 //左遍历结束并返回
        root->val:5 //右遍历开始
        root->val:3
        root->val:6 //右遍历中的左遍历
    */
    return countNodes(root->left) + countNodes(root->right) + 1;
}

4. LCP 44.开幕式焰火

LCP 44. 开幕式焰火

  • 题目

「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。
给定一棵二叉树 root 代表焰火,节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sZ59z6
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 思路分析

(参照博主的思路)

统计不同数的个数,可以采用的哈希表的方式。通过遍历的方式,标记数中所有的元素,重复数字赋相同标记值,便于统计个数。

知识补充

递归有两种形式:一种是带有返回值,一种是不带有返回值的。带有返回值的递归是明确了需要返回值,不带有返回值的递归,作为全局变量标记对应元素,不需要返回值。

memset()函数:用来给某个变量赋指定的初值

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
int hash[1024];
void transfer(struct TreeNode *root)
{
    //因为返回值没有确切的定义,所以无返回值,仅作为全局变量标记对应元素
    if(root)
    {
        hash[root->val] = 1;    //标记树中元素
        transfer(root->left);
        transfer(root->right);
    }
}
int numColor(struct TreeNode* root){
    //难点:怎么在递归中判断值不同==>标记
    int sum = 0;
    int i;
    //memset(void *, int c, size_t n)指定初始化为某个元素值
    memset(hash, 0, sizeof(hash));
    transfer(root);

    for(i = 0; i < 1001; ++i)
    {
        if(hash[i]) ++sum;  //为1说明元素至少出现一次,计数器加1
    }
    return sum;
}

5. 整数替换

397. 整数替换

  • 题目

给你一个 m 行 n 列的二维网格 grid 和一个整数 k。你需要将 grid 迁移 k 次。

每次「迁移」操作将会引发下述活动:

  • 位于 grid[i][j] 的元素将会移动到 grid[i][j + 1]

  • 位于 grid[i][n - 1] 的元素将会移动到 grid[i + 1][0]

  • 位于 grid[m - 1][n - 1] 的元素将会移动到 grid[0][0]

请你返回 k 次迁移操作后最终得到的 二维网格。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shift-2d-grid
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 题目分析

取偶数的递归实现,都比较容易实现,问题在如何实现奇数时的操作。题目要求的是最小的替换次数,那么在对奇数操作时,就需要判定哪一个操作数是最小的,最开始我使用的是每次递归加1后比较,未通过,理由是溢出。在参照官方思路之后,做出如下修改:将奇数变成偶数操作,那么每一次递归实际上就是操作了两次

  • 源码实现
int integerReplacement(int n){
    if(n == 1)
    {
        return 0;
    }

    if(!(n & 1)) //偶数
    {
        return integerReplacement(n / 2) + 1;
    }
    else
    {
        // n/2 <==> (n-1) / 2 (n此时是奇数,所以无需减一) n/2 + 1 <==> (n + 1) / 2
        //然后取最小值相加即可,这样写不会溢出
        return 2 + fmin(integerReplacement(n / 2), integerReplacement(n / 2 + 1)); //奇数变偶数相当于操作两次
    }

    //记忆化搜索
}

6. 二叉搜索数的范围和

938. 二叉搜索树的范围和

  • 题目

给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。

  • 题目分析

(代码和思路参照了官方,自己没想出来)求出对应的范围值的和,那么是不是就可以理解为排除所有范围外的值之后的,返回的结果。

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

int rangeSumBST(struct TreeNode* root, int low, int high){
    if(!root)
    {
        return 0;	//为空返回0,对结果加上0不改变原有的数值。
    }

    if(root->val < low) //小于,往右边找
    {
        return rangeSumBST(root->right, low, high);
    }
    if(root->val > high)//大于,往左边找
    {
        return rangeSumBST(root->left, low, high);
    }
    //符合条件的返回相加的结果
    return root->val + rangeSumBST(root->left, low, high) + rangeSumBST(root->right, low, high);

}

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

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

  • 题目

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

例如:

给定二叉树 [3,9,20,null,null,15,7],

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 题目分析

求深度就是每次遍历之后深度加1即可,需要注意的是求最大深度就需要比较左右子树遍历的次数,返回最大值即可

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */


int maxDepth(struct TreeNode* root){
    //每次往下遍历一次深度加1,统计每个深度,比较最终的深度
    if(!root)
        return 0;
    return fmax(maxDepth(root->left), maxDepth(root->right)) + 1;
}

8. 二叉树的最大深度

104. 二叉树的最大深度

  • 题目

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

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

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

  • 题目分析

和上题的解法一模一样

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */


int maxDepth(struct TreeNode* root){
      //每次往下遍历一次深度加1,统计每个深度,比较最终的深度
    if(!root)
        return 0;
    return fmax(maxDepth(root->left), maxDepth(root->right)) + 1;
}

9. 翻转二叉树

226. 翻转二叉树

  • 题目

翻转一棵二叉树。

  • 题目分析

翻转二叉树,实际上就是遍历然后交换左右子树的值。我原本的想法是交换两个变量的值,但总是会报错,因为空地址中无内容交换不了。

看了官方的题解才知道,只需要交换指针的指向就可以了,此刻陷入我严重怀疑自己的智商。

  • 源码实现
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* invertTree(struct TreeNode* root){
    if(!root)
    {
        return root;	//注意返回值,使用递归一定要想清楚返回值
    }
    struct TreeNode *left = invertTree(root->left);//遍历
    struct TreeNode *right = invertTree(root->right);
    root->left = right; //从下而上开始交换左右指针的指向
    root->right = left;
    
    return root;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值