文章目录
前言
一入递归深似海,做完题顺带还复习了树的相关知识,一举双得。
最后两题没什么时间做了,下次有时间在做吧
1. 阶乘后的零
- 题目
给定一个整数
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的操作次数
- 题目
给你一个非负整数
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.完全二叉树的节点个数
- 题目
给你一棵 完全二叉树 的根节点 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.开幕式焰火
- 题目
「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。
给定一棵二叉树 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. 整数替换
- 题目
给你一个 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. 二叉搜索数的范围和
- 题目
给定二叉搜索树的根结点
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.二叉树的深度
- 题目
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [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. 二叉树的最大深度
- 题目
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
- 题目分析
和上题的解法一模一样
- 源码实现
/**
* 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. 翻转二叉树
- 题目
翻转一棵二叉树。
- 题目分析
翻转二叉树,实际上就是遍历然后交换左右子树的值。我原本的想法是交换两个变量的值,但总是会报错,因为空地址中无内容交换不了。
看了官方的题解才知道,只需要交换指针的指向就可以了,此刻陷入我严重怀疑自己的智商。
- 源码实现
/**
* 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;
}