二叉树的相关题目

目录

1、根据二叉树创建字符串

2、二叉树的层序遍历

3、二叉树的最近公共祖先

 4、搜索二叉树与双向链表

 5、从前序与中序遍历序列构造二叉树

6、 从中序与后序遍历序列构造二叉树

7、二叉树的前序遍历(非递归实现)

8、二叉树的中序遍历(非递归实现)

9、二叉树的后序遍历(非递归实现)


1、根据二叉树创建字符串

题目要求:给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。


例1:

 前序遍历完应该是"1(2(3)())(5)",但是2没有右孩子,所以可以省略第一个括号

化简为:"1(2(3))(5)"


例2:

  前序遍历完应该是"1(2()(3))(5)",但是2没有左孩子,如果省略第一个括号,会辨别不清是左孩子还是右孩子

所以依旧为:"1(2()(3))(5)"


根据上面的样例,可以明白有这样几种情况:
①左右都不为空,则都不省略括号
②左右都为空,都省略括号
③左不为空,右为空,可以省略右括号
④左为空,右不为空,不能省略左括号
总结就是:如果右不为空,无论左边是否为空,右边都需要加括号
如果左不为空或右不为空,则左边需要加括号

代码如下:

class Solution {
public:string tree2str(TreeNode* root) {//若root为空,则返回一个string的匿名对象if(root == nullptr){return string();}//1、如果左不为空或右不为空,左边需要加括号//2、如果右不为空,右边需要加括号string str;//to_string将val转换为字符变量,以便可以+=str += to_string(root->val);//情况1if(root->left || root->right){str += '(';str += tree2str(root->left);str += ')';}//情况2if(root->right){str += '(';str += tree2str(root->right);str += ')';}return str;}
};

2、二叉树的层序遍历

 题目要求:给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。


思路分析:

我们可以创建一个队列,队列中存的是二叉树的指针,再给一个levelSize,记录每一层的节点数,在循环过程中,创建一个vector<int>的数组,存每一层的结点val值。首先,二叉树若不为空则将root存进队列中,再经过判断将root的左右孩子存进队列中,队列头结点pop前,都将val存入v中,每层结束,都将v的值push_back到vv中,以此类推,具体代码中注释部分有


代码:

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {//层序遍历一般会使用队列queue<TreeNode*> q;//levelSize是每一层的节点数size_t levelSize = 0;//如果根节点不为空,则队列中插入root,节点数置为1if(root){q.push(root);levelSize = 1;}//vv是需要返回的vector<vector<int>>vector<vector<int>> vv;//while循环,直到队列为空while(!q.empty()){//创建vector<int> v,存储每一层的结点的valvector<int> v;//for循环保证每次循环一层的结点for(size_t i = 0;i < levelSize; ++i){//由于每次都要删除队列的第一个值//所以front来保留一下指针,以免找不到左右字树TreeNode* front = q.front();q.pop();//每次删除的时候都存进vv.push_back(front->val);//如果删除结点有左右孩子,都存进队列中if(front->left)q.push(front->left);if(front->right)q.push(front->right);          }//每循环完一层,就往vv里存一层的val值vv.push_back(v);//接着重新赋值levelSize,即下一层数的节点数levelSize = q.size();}return vv;}
};

3、二叉树的最近公共祖先

题目要求:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

方法一例1:

 则最近公共祖先是结点2

方法一例2:

 则最近公共祖先是结点4

所以方法一我们可以用下面两个思路:

1、如果一个是左子树中的结点,一个是右子树中的结点,那么它就是最近公共祖先

2、如果一个结点A是结点B的祖先,那么公共祖先就是结点A

方法一的代码:(方法一如果遇到公共祖先在二叉树下面的部分,会导致效率比较低)

class Solution {
public:bool Find(TreeNode* root, TreeNode* x){//如果查找的为空,返回nullptrif(root == nullptr)return false;//如果找到了,返回trueif(root == x)return true;//如果没找到,则递归进左右字树找return Find(root->left, x) || Find(root->right, x);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == nullptr)return nullptr;//说明公共祖先是root,if((root == p) || (root == q))return root;//p/q在一左一右,则说明当前root是公共祖先//设定4个bool类型变量,与Find结合使用bool pInLeft,pInRight,qInleft,qInRight;pInLeft = Find(root->left, p);pInRight = !pInLeft;//在左就说明不在右,所以可以用!qInleft = Find(root->left, q);qInRight = !qInleft;//一个在左一个在右,则它是公共祖先if((pInLeft && qInRight) || (pInRight && qInleft))return root;//若都在root左或右,则递归进左或右子树中,重新判断上面的条件else if(pInLeft && qInleft)return lowestCommonAncestor(root->left, p, q);else if(pInRight && qInRight)return lowestCommonAncestor(root->right, p, q);//此题不会进入这里,因为p/q都在二叉树中elsereturn nullptr;}
};

方法二思路:(相比方法一效率高点,O(N))

将p和q的从根结点开始的路径放入栈中,将所得两个结点的较长的路径pop到和较短路径一样长为止,然后依次判断栈顶元素是否相同

思路类似链表相交

方法二例子:

结点3和结点1的路径放栈里如图:

 结点1路径长度大,pop相等后变为:

 接着从两个栈顶元素3和9开始判断,不相同,两个都pop,直到遇到2,返回结点2

方法二代码:

class Solution {
public:bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path){//是空返回falseif(root == nullptr)return false;//不论是不是先入栈,因为后面判断不是路径会poppath.push(root);//如果找到了,返回trueif(root == x)return true;//如果没找到,进入左子树找if(FindPath(root->left,x,path))return true;//如果左子树没找到,进入右子树找if(FindPath(root->right,x,path))return true;//左右字树都没找到,pop掉当前栈顶元素,返回falsepath.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//栈的每个元素都是TreeNode*类型stack<TreeNode*> pPath,qPath;//FindPath中传入的pPath和qPath都是p和q从根结点的路径FindPath(root, p, pPath);FindPath(root, q, qPath);//p/q结点的路径长度不同,先变为相同路径长度while(pPath.size() != qPath.size()){if(pPath.size() > qPath.size())pPath.pop();elseqPath.pop();}//相同路径长度一层层判断顶部元素是否相同while(pPath.top() != qPath.top()){pPath.pop();qPath.pop();       }//走到这里说明找到了相同的结点,即最近祖先return pPath.top();}
};

 4、搜索二叉树与双向链表

题目要求:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示:

1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构


思路分析:

由于不能创建新的结点,只能调整树中结点指针的指向,所以我们就不能用先中序排好序以后,再遍历的方法

那么就在中序遍历的过程中,给两个指针,一个prev,一个cur,prev是指向前一个结点,cur是值向当前的结点,每次cur变化前,都将值赋值给prev,然后再将cur->left指向prev,以此类推完成了left指针,当前的prev就是上一个cur,所以prev->right = cur就是相当于上一个cur->right也指向了下一个结点,从而完成了right指针


代码:

class Solution {
public://中序遍历,并在过程中调整结点指针的指向//cur是当前结点的指针,prev是前一个结点的指针void Inorder(TreeNode* cur,TreeNode*& prev){if(cur == nullptr)return;//先左子树Inorder(cur->left,prev);//cur->left直接给prev,因为prev是前一个结点指针cur->left = prev;//若prev不为空,且为TreeNode*& prev,是传引用,即://prev->right就完成了上一个cur结点的right指针指向if(prev)prev->right = cur;//在cur指向下一个之前,赋值给prevprev = cur;//再右子树Inorder(cur->right,prev);}TreeNode* Convert(TreeNode* pRootOfTree) {//创建一个prev置空,传入Inorder进行中序排序TreeNode* prev = nullptr;Inorder(pRootOfTree, prev);//head先指定为题目所给的根结点TreeNode* head = pRootOfTree;//顺着left指针找到中序遍历的第一个结点//为了防止pRootOfTree为空,要先判断headwhile(head && head->left)head = head->left;//返回第一个结点指针return head;}
};



 5、从前序与中序遍历序列构造二叉树

题目要求:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历 inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

 思路:

通过前序遍历确定根,通过中序遍历确定左右字树

子树区间确认是否继续递归创建子树,不存在区间则是空树

代码:

class Solution {
public://创建_buildTree函数进行递归调用//prei是前序遍历结果的首元素下标,inbegin、inend是中序遍历结果首尾元素的下标TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend){//如果在前序遍历的结果中找,if(inbegin > inend)return nullptr;//每次递归通过前序遍历结果创建根结点TreeNode* root = new TreeNode(preorder[prei++]);//while循环找到中序遍历的该结点的位置int cur = inbegin;while(cur <= inend){if(inorder[cur] == root->val)break;elsecur++;}//中序遍历的结果中,分成了三个部分,[左子树]根[右子树]//[inbegin, cur-1] cur [cur+1,inend]//所以接下来递归时,传入这两个区间root->left = _buildTree(preorder,inorder,prei,inbegin,cur-1);root->right = _buildTree(preorder,inorder,prei,cur+1,inend);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {//前序遍历首元素下标为0int prei = 0;//中序遍历结果首尾元素的下标为0和inorder.size()-1TreeNode* root = _buildTree(preorder,inorder,prei,0,inorder.size()-1);return root;}
};




6、 从中序与后序遍历序列构造二叉树

题目要求:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。


这个题和上面的从前序与中序遍历序列构造二叉树大体思路一样,但是由于是后序确定根结点,所以给定后序遍历结果的下标posi,每次都会posi--,并且是先递归右子树,再递归左子树,因为后序遍历顺序是左子树,右子树,根结点,反过来就是根结点,右子树,左子树

所以代码如下:

class Solution {
public:TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder,int& posi,int inbegin,int inend){if(inbegin > inend)return nullptr;TreeNode* root = new TreeNode(postorder[posi--]);int cur = inbegin;while(cur <= inend){if(root->val == inorder[cur])break;elsecur++;}//[inbegin,cur-1] cur [cur+1,inend]root->right = _buildTree(inorder,postorder,posi,cur+1,inend);root->left = _buildTree(inorder,postorder,posi,inbegin,cur-1);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int posi = postorder.size()-1;return _buildTree(inorder,postorder,posi,0,inorder.size()-1);}
};


7、二叉树的前序遍历(非递归实现)

题目要求:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。


思路分析

采用非递归实现该问题,能提高效率,并且递归调用需要建立栈帧,如果深度比较深会容易崩溃,所以需要掌握非递归的方法

前序遍历中,我们可以将所有结点分为左路结点,以及左路结点的右子树

那么我们第一步就是将左路结点都保存下来,并且存在栈中,接着将存入栈的结点一个一个出栈,并访问右子树,然后重复上面的步骤(左路结点保存,入栈,全部入栈后,然后出栈,访问该出栈结点的右子树)

左路结点从栈中出来时,表示左子树以及访问过了,该访问该结点和它的右子树了

就相当于转换为了子问题,将所有结点分为左路结点,以及左路结点的右子树,接着将左路结点的右子树又分为:左路结点,以及左路结点的右子树以此类推,从而实现非递归的方法完成前序遍历


代码如下:

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;//循环条件有两个都不符合才结束循环//一是栈里空,表明初始的左路结点的右子树都已访问//二是cur为空,表明访问的栈中的结点的右子树为空while(cur || !st.empty()){//1、左路结点while(cur){v.push_back(cur->val);st.push(cur);cur = cur->left;}//2、左树结点的右子树TreeNode* top = st.top();st.pop();//将左路结点以外的数转化为上面两条的子问题//转换为子问题从而访问栈中结点的右子树cur = top->right;}return v;}
};


8、二叉树的中序遍历(非递归实现)

题目要求:给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。


中序遍历和前序遍历思路大体相同,但是由于中序遍历是:左子树,根,右子树。所以中序遍历的结果需要在左路结点都入栈后,再依次push_back进数组中,剩下思路和前序遍历相同


代码如下:

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;while(cur || !st.empty()){while(cur){st.push(cur);cur = cur->left;}//左路结点都入栈后,再尾插栈顶元素到数组中//依次取栈顶元素,再pop,转换为子问题循环TreeNode* top = st.top();   st.pop();v.push_back(top->val);cur = top->right;}return v;}
};



9、二叉树的后序遍历(非递归实现)

题目要求:给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 


思路分析:

后序遍历和前序/中序有一点区别,因为后序是左子树,右子树,根,我们先找到左路结点后,无法确认该结点的右子树有没有访问,所以就这一问题可以分类讨论

设定一个prev结点,让他指向cur结点的前一个结点,即每次尾插入数组时都记录当前的结点值,赋值给prev,这样在cur = cur->right以后,prev就是cur所访问的前一个结点。

将所有左路结点全部插入到栈以后,分为两种情况:

第一:该结点的右子树为空或该结点的右子树已经访问过了第二:该结点的右子树没有被访问过

第一种情况就可以访问这个栈顶结点否则先访问该结点的右子树,转换为了子问题


代码:

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;TreeNode* prev = nullptr;while(cur || !st.empty()){//左路结点入栈while(cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();//右子树为空或上一个访问的就是该结点的右子树的根//说明右子树已经访问过了if(top->right == nullptr || top->right == prev){v.push_back(top->val);prev = top;cur = nullptr;st.pop();}//否则先访问栈顶结点的右子树else{cur = top->right;}}return v;}
};


相关题目列举这些


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/20868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring — Spring Security 5.7与6.0差异性对比

1. spring security Spring Security 是一个提供身份验证、授权和针对常见攻击保护的框架。 凭借对保护命令式和反应式应用程序的一流支持&#xff0c;它成为基于Spring的标准安全框架。 Spring Security 在最近几个版本中配置的写法都有一些变化&#xff0c;很多常见的方法都…

宇凡微2.4g遥控船开发方案,采用合封芯片

2.4GHz遥控船的开发方案是一个有趣且具有挑战性的项目。这样的遥控船可以通过无线2.4GHz频率进行远程控制&#xff0c;让用户在池塘或湖泊上畅游。以下是一个简要的2.4GHz遥控船开发方案&#xff1a; 基本构想如下 mcu驱动两个小电机&#xff0c;小电机上安装两个螺旋桨&#…

在使用Python爬虫时遇到503 Service Unavailable错误解决办法汇总

在进行Python爬虫的过程中&#xff0c;有时会遇到503 Service Unavailable错误&#xff0c;这意味着所请求的服务不可用&#xff0c;无法获取所需的数据。为了解决这个常见的问题&#xff0c;本文将提供一些解决办法&#xff0c;希望能提供实战价值&#xff0c;让爬虫任务顺利完…

Docker中gitlab以及gitlab-runner的安装与使用

1、本文主要讲述如何使用Docker安装gitlab以及gitlab-runner&#xff0c;并且会讲述gitlab-runner如何使用 2、gitlab部分不需要修改过多的配置即可使用&#xff0c;本文未讲述https配置&#xff0c;如有需求&#xff0c;可自行百度 3、Docker如何安装可以自行百度 一、Docker安…

轻量化YOLOv5改进 | 结合repghost结构冲参数化网络,实现轻量化和加速推理,

RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization 论文总结本文改进repghost 核心代码测试参数量和计算量🔥🔥🔥 “引入RepGhostNet以加速CNN网络推理” “网络宽度的自定义调整:无缝嵌入YOLOv5” “通过结构重参数化优化网络性能” “实现高效和…

【JVM】(二)深入理解Java类加载机制与双亲委派模型

文章目录 前言一、类加载过程1.1 加载&#xff08;Loading&#xff09;1.2 验证&#xff08;Verification&#xff09;1.3 准备&#xff08;Preparation&#xff09;1.4 解析&#xff08;Resolution&#xff09;1.5 初始化&#xff08;Initialization&#xff09; 二、双亲委派…

数字化采购:提升效率、优化供应链的新趋势

随着信息技术的快速发展&#xff0c;数字化采购正成为企业追求效率和优化供应链的新趋势。数字化采购是利用数字技术和互联网平台&#xff0c;实现采购流程的自动化和在线化。本文将围绕数字化采购的应用场景&#xff0c;探讨其在采购环节中带来的效益与优势。 一、在线供应商…

分布式限流方案及实现

优质博文&#xff1a;IT-BLOG-CN 一、限流的作用和意义 限流是对高并发访问进行限制&#xff0c;限速的过程。通过限流来限制资源&#xff0c;可以提高系统的稳定性和可靠性&#xff0c;控制系统的负载&#xff0c;削峰填谷&#xff0c;保证服务质量。 服务限流后的常见处理…

重磅!EBImage包:为何如此火爆?它的图像处理到底有何不可思议之处?

一、简介 1.1 EBImage包简介 EBImage包是一个广受欢迎的用于图像处理和分析的R语言包。它提供了一套全面而强大的功能&#xff0c;支持多种图像格式的读取和写入&#xff0c;处理多维图像数据&#xff0c;并提供了各种先进的图像处理算法、特征提取和测量函数。 1.2 EBImage爆火…

DAY1,C高级(命令,Linux的文件系统,软、硬链接文件)

1.创建链接文件&#xff1b; 文件系统中的每个文件都与唯一的 inode 相关联&#xff0c;inode 存储了文件的元数据和数据块的地址&#xff0c;文件名与 inode 之间的链接关系称为硬链接或软链接。 硬链接文件的创建&#xff1a; ln 被链接文件的绝对路径 硬链接文件的绝对…

nsqd的架构及源码分析

文章目录 一 nsq的整体代码结构 二 回顾nsq的整体架构图 三 nsqd进程的作用 四 nsqd启动流程的源码分析 五 本篇博客总结 在博客 nsq整体架构及各个部件作用详解_YZF_Kevin的博客-CSDN博客 中我们讲了nsq的整体框架&#xff0c;各个部件的大致作用。如果没看过的&…

论文笔记——Influence Maximization in Undirected Networks

Influence Maximization in Undirected Networks ContributionMotivationPreliminariesNotations Main resultsReduction to Balanced Optimal InstancesProving Theorem 3.1 for Balanced Optimal Instances Contribution 好久没发paper笔记了&#xff0c;这篇比较偏理论&…

pytorch 中 view 和reshape的区别

在 PyTorch&#xff08;一个流行的深度学习框架&#xff09;中&#xff0c; reshape 和 view 都是用于改变张量&#xff08;tensor&#xff09;形状的方法&#xff0c;但它们在实现方式和使用上有一些区别。下面是它们之间的主要区别&#xff1a; 实现方式&#xff1a; reshap…

html学习7(iframe)

1、通过使用iframe标签定义框架&#xff0c;可在同一个浏览器中显示不止一个画面。 2、height和width属性用于定义框架的高度与宽度。 3、属性frameborder‘0’用于是否显示边框。 4、iframe可以显示一个目标链接的页面&#xff0c;链接的target属性设置为相应的iframe名称。…

2023年华数杯C题思路

c题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一,她不仅为婴儿提供营养物质和身体保护,还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑压力等&#xff0c;可能会对婴儿的认知、情感、社会行为等方面产生负面影响。压力…

2023年华数杯C题详细思路

2023年华数杯作为与国赛同频的比赛&#xff08;都是周四6点发题&#xff0c;周日晚8点交卷&#xff09;&#xff0c;也是暑期唯一一个正式比赛。今年的报名队伍已经高达6000多对。基于这么多的人数进行国赛前队伍的练习&#xff0c;以及其他用途。为了方便大家跟更好的选题&…

机器学习03-数据理解(小白快速理解分析Pima Indians数据集)

机器学习数据理解是指对数据集进行详细的分析和探索&#xff0c;以了解数据的结构、特征、分布和质量。数据理解是进行机器学习项目的重要第一步&#xff0c;它有助于我们对数据的基本属性有全面的了解&#xff0c;并为后续的数据预处理、特征工程和模型选择提供指导。 数据理解…

vue 图片回显标签

第一种 <el-form-item label"打款银行回单"><image-preview :src"form.bankreceiptUrl" :width"120" :height"120"/></el-form-item>// 值为 https://t11.baidu.com/it/app106&fJPEG&fm30&fmtauto&…

SpringBoot整合Caffeine

一、Caffeine介绍 1、缓存介绍 缓存(Cache)在代码世界中无处不在。从底层的CPU多级缓存&#xff0c;到客户端的页面缓存&#xff0c;处处都存在着缓存的身影。缓存从本质上来说&#xff0c;是一种空间换时间的手段&#xff0c;通过对数据进行一定的空间安排&#xff0c;使得下…

如何使用免费敏捷工具Leangoo领歌管理Sprint Backlog

什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优先…