【C++】详解二叉树进阶OJ题(更深入理解递归和非递归在二叉树的各种结构的应用)

news2025/1/15 4:28:17

前言:

    我们在此前的初阶数据结构讲解中已经讲解了部分二叉树的OJ题,当时我们只学习了C语言,其实还有很多进阶的OJ题用C++来写会比较方便和容易理解,所以本章将在讲解完二叉搜索树后来详解不同类型的二叉树进阶OJ题,校招中涉及也比较多哦!

目录

(1)根据二叉树创建字符串

(2)二叉树的层序遍历(一)

(3)二叉树的层序历遍(二)

(4)二叉树的最近公共祖先

(5)二叉搜索树与双向链表

(6)根据一棵树的前序遍历与中序遍历构造二叉树

 (7)根据一棵树的中序遍历与后序遍历构造二叉树

(8)非递归迭代实现二叉树的前序遍历

 (9)非递归迭代实现二叉树的中序遍历

 (10)非递归迭代实现二叉树的后序遍历


(1)根据二叉树创建字符串

力扣https://leetcode.cn/problems/construct-string-from-binary-tree/

 

 解析:

这道题目基本就是一个前序排序,但是要额外加上左右括号。

需要特别注意的是,有的空括号要保留,而有的空括号我们则要省略。

我们观察分析上面的两个样例:

  • 当一个结点的左子树为空,但右子树不为空时我们要加空括号来确定;
  • 如果同时为空或者右子树为空,则可以省略。

我们采用递归的思想前序历遍,加以条件判断并加上左右括号:


class Solution {
public:
    string tree2str(TreeNode* root) {

     if(root==nullptr)
     return "";

     string str=to_string(root->val);
     if(root->left||root->right)
     { 
         str+='(';
         str+=tree2str(root->left);
         str+=')';
         }
    
     if(root->right)
     {
          str+='(';
          str+=tree2str(root->right);
          str+=')';
     }
    
    return str;
    }
};

(2)二叉树的层序遍历(一)

力扣https://leetcode.cn/problems/binary-tree-level-order-traversal/

 

 解析:

我们仔细观察会发现本题就是层序历遍二叉树并每层的结点存放在一个二维数组里。

难点在于:

  • 一是如何层序历遍二叉树;
  • 二是每层的结点数目不一样,怎样把握存放在二维数组的同一行中。

我们带着疑问来进一步分析:

层序历遍二叉树,我们此前其实学过,再带领大家回忆一下:

我们要有一个队列,先存放根节点:

 然后记录队列的头并pop掉,然后带着他的左右孩子入队列:

 我们再重复上面的操作直到队列为空和访问完所有结点为止!


二是我们如何让每一层的结点恰好存放在二维数组的每一行中?

我们可以设置一个levelsize变量,根节点进完队列就为1,然后用循环控制pop的次数,此时只有根节点循环一次即可 ,最后入完左右孩子在把队列的长度赋给levelsize,重复这样的操作即可。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
int levelsize=0;
if(root)
{
    q.push(root);
    levelsize=1;
}
vector<vector<int>> vv;
while(!q.empty())
{
vector<int> v;
while(levelsize--)
{
TreeNode* front=q.front();
q.pop();
v.push_back(front->val);

if(front->left)
q.push(front->left);

if(front->right)
q.push(front->right);
}

vv.push_back(v);
levelsize=q.size();
}
return vv;
    }
};

(3)二叉树的层序历遍(二)

力扣https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/

 这道题目就有点像上面那道题目的进阶版本。

乍一看感觉这道题目很恐怖,我们又没学过从底层向上的层序遍历啊,这该怎么办?

这里大家观察一下样例的输出结果和第二道题样例的输出结果:、

 哈哈,想必大家看出端倪了吧,其实就是二维数组中行的逆序,同样的方法,控制好最后的逆序就行了:

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {

queue<TreeNode*> q;
int levelsize=0;
if(root)
{
    q.push(root);
    levelsize=1;
}
vector<vector<int>> vv;
while(!q.empty())
{
vector<int> v;
while(levelsize--)
{
TreeNode* front=q.front();
q.pop();
v.push_back(front->val);

if(front->left)
q.push(front->left);

if(front->right)
q.push(front->right);
}

vv.push_back(v);
levelsize=q.size();
}
reverse(vv.begin(),vv.end());
return vv;
    }
};

(4)二叉树的最近公共祖先

力扣https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

 

解析:

 这道题下面作者会给出两种思路。

思路一:

我们通过观察其实这道题目的公共祖先可以转化为,这两个结点在从上往下的哪个结点的两边。

class Solution {
public:
    bool IsInTree(TreeNode* root, TreeNode* x)
    {
        if(root==nullptr)
        return false;

        
        return root==x
            ||IsInTree(root->left,x)
            ||IsInTree(root->right,x);
        
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)
        return nullptr;

        if(p==root||q==root)
        return root;

       
            bool PInleft=IsInTree(root->left,p);
            bool PInRight=!PInleft;

            bool QInleft=IsInTree(root->left,q);
            bool QInRight=!QInleft;
            

            if((PInleft&&QInRight)||(PInRight&&QInleft))
            return root;

            else if(PInleft&&QInleft)
            return lowestCommonAncestor(root->left,p, q);

            else
            return lowestCommonAncestor(root->right,p, q);



    }
};

思路二:

求出这两个结点到根节点的路径,按照链表那一章讲过的方法,找到两条路径第一个公共结点。

ps:由于空间复杂度的限制以及结点没有指向父结点的指针,我们在实现思路二时候需要用到栈。

class Solution {
public:
bool Getpath(TreeNode* root,TreeNode* x,stack<TreeNode*>& path)
{
    if(root==nullptr)
    return false;

    path.push(root);
    if(root==x)
    return true;

    if(Getpath(root->left,x,path))
    return true;

    if(Getpath(root->right,x,path))
    return true;

    path.pop();
    return false;
}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pPath;
        stack<TreeNode*> qPath;

        Getpath(root,p,pPath);
        Getpath(root,q,qPath);

        while(pPath.size()!=qPath.size())
        {
            if(pPath.size()>qPath.size())
            pPath.pop();
            else
            qPath.pop();
        }

        while(pPath.top()!=qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }

return pPath.top();
    }
};

(5)二叉搜索树与双向链表

二叉搜索树与双向链表_牛客题霸_牛客网【牛客题霸】收集各企业高频校招笔面试题目,配有官方题解,在线进行百度阿里腾讯网易等互联网名企笔试面试模拟考试练习,和牛人一起讨论经典试题,全面提升你的技术能力https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&&tqId=11179&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

 

 解析:

这道题目有点类似线索二叉树。

这里我们要注意这道题目要求的空间复杂度是O(1),所以我们必须在原树上操作。

我们答题思路就是:

  • 中序历遍二叉树,并且结点相互链接上。
  • 主要就是如何实现链接,主要用两个变量prev和cur保存历遍前后的地址然后链接、移动...

请看代码:


class Solution {
public:
    void InOrder(TreeNode* cur,TreeNode*& prev)
	{
    if(cur==nullptr)
	return;

	InOrder(cur->left,prev);
	
	cur->left=prev;
    if(prev)
	prev->right=cur;
	prev=cur;//链接过程

    InOrder(cur->right,prev);
	}


    TreeNode* Convert(TreeNode* pRootOfTree) {
        TreeNode* prev=nullptr;
    InOrder(pRootOfTree,prev);

	TreeNode* root=pRootOfTree;
	while(root&&root->left)
	root=root->left;

	return root;
    }
};

6)根据一棵树的前序遍历与中序遍历构造二叉树

力扣https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

解析:

我们以实例1为例,我们取preorder的第一个,其实也就是根节点,我们再把根节点对应在中序序列中观察:

 我们发现在中序序列中,3的左侧是他的左子树,右侧是他的右子树。

我们再在前序序列中看下一个数:

 9的左右子树都是空,所以在序列中只有9,再继续看下一个:

我么你发现了3右侧这个区间中,20的左侧是左子树,20的右侧是他的右子树,此时我们便大致了解了规律。

class Solution {
public:
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]);
int rooti=inbegin;
while(rooti<=inend)
{
    if(inorder[rooti]!=preorder[prei])
    {
        rooti++;
    }
    else
    {
        break;
    }}
    ++prei;
    root->left=_buildTree(preorder,inorder,prei,inbegin,rooti-1);
    root->right=_buildTree(preorder,inorder,prei,rooti+1,inend);
    return root;
}
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int prei=0;
return _buildTree(preorder,inorder,prei,0,inorder.size()-1);
    }
};

 注意区间范围,此外大家可以画递归展开图理解哦!

 (7)根据一棵树的中序遍历与后序遍历构造二叉树

力扣https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/

 解析:

这道题和上面一道题类似,我们需要应用好后序和中序的关系,从后面开始历遍。

ps:这里需要注意的是,后序历遍是先历遍右树,再历遍左树:

class Solution {
public:
TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder,int& i,int inbegin,int inend)
{
if(inbegin>inend)
return nullptr;
int rooti=inbegin;
TreeNode* root=new TreeNode(postorder[i]);
while(rooti<=inend)
 { if(inorder[rooti]!=postorder[i])
    {
        rooti++;
    }
    else
    {
        break;
    }
}

--i;
root->right=_buildTree(inorder,postorder,i,rooti+1,inend);
root->left=_buildTree(inorder,postorder,i,inbegin,rooti-1);

return root;
}

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) 
    {
    int i=postorder.size()-1;
    return _buildTree(inorder,postorder,i,0,inorder.size()-1);
    }
};

(8)非递归迭代实现二叉树的前序遍历

力扣https://leetcode.cn/problems/binary-tree-preorder-traversal/

 解析:

对于二叉树的遍历,我们用递归实现很简单。但是如何使用非递归实现呢?

我们借用递归的思路,用栈来模拟递归的过程。

我们使用栈来进行迭代,过程如下:

  • 初始化栈,并将根节点入栈;
  • 当栈不为空时:
  • 弹出栈顶元素 node,并将值添加到结果中;
  • 如果 node 的右子树非空,将右子树入栈;
  • 如果 node 的左子树非空,将左子树入栈;
  • 由于栈是“先进后出”的顺序,所以入栈时先将右子树入栈,这样使得前序遍历结果为 “根->左->右”的顺序。

 

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

vector<int> v;
stack<TreeNode*> st;
TreeNode* cur=root;
while(cur||!st.empty())
{
    while(cur)
    {
        st.push(cur);
        v.push_back(cur->val);
        cur=cur->left;
    }
    TreeNode* top=st.top();
    st.pop();
    cur=top->right;
}

return v;
    }
};

 (9)非递归迭代实现二叉树的中序遍历

力扣https://leetcode.cn/problems/binary-tree-inorder-traversal/

 解析:

中序遍历可以参考前序遍历的过程,思路是一样的。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> v;
TreeNode* cur=root;

while(cur||!st.empty())
{
    while(cur)
    { 
        st.push(cur);
        cur=cur->left;
    }
     TreeNode* top=st.top();
     st.pop();
     v.push_back(top->val);
     cur=top->right;

}
return v;
    }
};

 (10)非递归迭代实现二叉树的后序遍历

力扣https://leetcode.cn/problems/binary-tree-postorder-traversal/

 

 相对于前序,中序来说,后序的实现过程更麻烦一点。

这里我们呢还是像前面一样通过循环找到最左的元素(注意保存在栈中),但是要注意:迭代写法,利用pre记录上一个访问过的结点,与当前结点比较,如果是当前结点的子节点,说明其左右结点均已访问,将当前结点出栈,更新pre记录的对象。

循环过程如下: 

完整代码:
 

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)
{
st.pop();
v.push_back(top->val);
prev=top;
}
else
{
    cur=top->right;
}
}
return v;
    }
};

ps:取巧的方法。该写法的访问顺序并不是后序遍历,而是利用先序遍历“根左右”的遍历顺序,将先序遍历顺序更改为“根右左”,反转结果List,得到结果顺序为“左右根”。(不建议用)

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

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

相关文章

常用性能测试工具都有哪些特点?

在软件开发过程中&#xff0c;性能测试是不可或缺的一部分。通过性能测试&#xff0c;我们可以评估系统的响应速度、稳定性和容量等方面的表现&#xff0c;帮助我们发现和解决潜在的性能问题。而常用的性能测试工具也在不断发展和升级&#xff0c;成为了帮助我们完成性能测试的…

【ArcGIS Pro二次开发】(25):属性映射

属性映射经常用于属性表或Excel表的赋值&#xff0c;比如按用地用海表对规划用地的用地编码或用地名称赋值&#xff0c;将汇总好的用地指标表赋值给已经制好的Excel模板等。 下面试着在ArcGIS Pro SDK中实现功能上述这两个功能。 一、Excel表格映射到属性表Table 1、要实现的…

VMware虚拟机安装CentOS8详细教程

文章目录 一、下载安装包二、创建虚拟机1.安装 VMware2.创建虚拟机3.编辑虚拟机设置 三、系统安装1.开始安装2.时区设置3.分区设置4.配置网络6.开机密码7.配置安装源8.安装 四、系统配置1.网络检查2.配置静态IP地址 一、下载安装包 CentOS-7-x86_64【Minimal版】 https://mirro…

程序员必知必会!阿里内部热捧“Spring全线笔记”太完整了

前言 对于每一位Java开发人员来说&#xff0c;提起Spring定是不陌生的&#xff0c;实际上自Spring框架诞生以来&#xff0c;就备受开发者的青睐&#xff0c;基本上现在的互联网公司都要使用到Spring框架。Spring框架中又包含了SpringMVC、SpringBoot、SpringCloud等&#xff0…

OpenGL光照教程之 光照贴图

引言 前面的教程&#xff0c;我们讨论了让不同的物体拥有各自不同的材质并对光照做出不同的反应的方法。在一个光照场景中&#xff0c;让每个物体拥有和其他物体不同的外观很棒&#xff0c;但是这仍然不能对一个物体的图像输出提供足够多的灵活性。  前面的教程中我们将一个物…

关于FPGA基础知识 LCMXO2-7000HC-4TG144C MachXO2系列 FPGA可编程逻辑简介

关于FPGA基础知识 LCMXO2-7000HC-4TG144C lattice莱迪斯深力科 MachXO2系列 FPGA可编程逻辑简介 FPGA基础知识&#xff1a;FPGA是英文Field&#xff0d;Programmable Gate Array的缩写&#xff0c;即现场可编程门阵列&#xff0c;它是在PAL、GAL、CPLD等可编程器件的基础上进一…

【测评】飞凌i.MX8MM开发板,为你带来卓越的影音体验

来源&#xff1a;飞凌嵌入式官网 OKMX8MM-C是飞凌基于NXP公司i.MX8M Mini 四核64位处理器所设计的一款开发板&#xff0c;主频最高达1.8GHz&#xff0c;可提供多种音频接口&#xff0c;包括I2S、AC97、TDM、PDM和SPDIF。在性能和算力都大幅提高的同时&#xff0c;系统的运行也更…

idea配置Tomcat服务和创建javaweb项目

前言 我的idea版本是Ultimate 2022.3 步骤 1.先创建一个空的java项目 2.点击project structure 然后点击moudle – > dependcies —>点&#xff0b; 选择JArs or … 找到你安装的tomcat里面的lin依次添加jsp-api.jar、servlet-api.jar 右击项目然后点add Framework s…

SPSS如何进行生存分析之案例实训?

文章目录 0.引言1.寿命表分析2.Kaplan-Meier分析方法3.Cox回归分析 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对生存分析进行阐…

JVM虚拟机中的类加载机制和双亲委派模型

目录 虚拟机的类加载机制 名词解释 类加载的时机 类加载的过程 1.加载&#xff08;Loading&#xff09;阶段 非数组类型的加载阶段与数组类型区别 2.验证&#xff08;Verification&#xff09;阶段 1&#xff09;文件格式验证 2&#xff09;元数据验证 3&#xff09;…

深入理解Java虚拟机——对象的创建和内存布局

1.对象的创建 首先声明这一篇博客是在HotSpot虚拟机的前提之下记录的。主要参考书籍来源于周志明老师的《深入理解JVM虚拟机》。 在语言层面&#xff0c;创建对象仅仅是使用一个new关键字。但是从虚拟机的角度来看&#xff0c;创建一个对象一共有5个步骤&#xff1a;类加载检查…

排序大师:探秘C语言中神奇的qsort库函数

本篇文章中会详细讲解C语言中的qsort库函数。我准备分2个方面来讲&#xff1a; qsort如何使用。模拟实现qsort的效果。&#xff08;注意&#xff1a;只是用冒泡排序的思想实现类似的效果&#xff0c;实际qsort的底层采用的是快速排序的思想。&#xff09; 如何使用 先来看看q…

反调试与反反调试

参考文本 (190条消息) C 反反调试&#xff08;NtQueryInformationProcess&#xff09;_(-: LYSM :-)的博客-CSDN博客 Windows 平台反调试相关的技术方法总结—part 2 - 先知社区 C/C MinHook 库的使用技巧 - lyshark - 博客园 (cnblogs.com) (177条消息) C 反反调试&#x…

C结构简单而不失强大的表格

2023年了&#xff0c;想必已经不会有人对嵌入式开发中“数据结构&#xff08;Data Structure&#xff09;”的作用产生疑问了吧&#xff1f;无论你是否心存疑惑&#xff0c;本文都将给你一个完全不同的视角。 每每说起数据结构&#xff0c;很多人脑海里复现的一定是以下的内容&…

unity中用异步的whenAny,实现:当点击铲子任一部件,拾取整个铲子

一、铲子的组成 铲子包含很多部件组成&#xff0c;当拾取铲子的时候&#xff0c;只要点击铲子的任意一个部件就可以。 如图&#xff0c;点击【木柄】、【螺母】、【铁铲】都可以拾取该物体。 &#xff08;1&#xff09;打开高亮 &#xff08;2&#xff09;等待土铲被点击&…

为什么要通过API接口来获取数据

API接口&#xff08;应用编程接口 application/programming接口&#xff09;&#xff0c;准许应用程序通过定义的接口标准来访问另一个应用程序或服务的编程方式。简单来说&#xff0c;API就是两个软件或系统之间的通信语言或接口。 在当今的互联网时代&#xff0c;数据无处不…

Geospatial和Redis事务操作

一、Geospatial 1.简介 基于位置信息服务 (Location-Based Service,LBS) 的应用。 Redis3.2 版本后增加了对 GEO 类型的支持。主要来维护元素的经纬度。redis 基于这种类型&#xff0c;提供了经纬度设置、查询、范围查询、距离查询、经纬度hash等一些相关操作。 2.GEO底层结构…

DataEase 数据源插件分享 - 时序数据库 InfluxDB

前言 InfluxDB 是一个时序数据库&#xff0c;使用的是非标准的 SQL 语法&#xff0c;我使用 DataEase 的插件扩展机制开发了此数据源插件&#xff0c;在这里共享出来&#xff0c;想用的朋友可以下载安装使用。 插件包下载地址 https://north-dataease-1251506367.cos.ap-bei…

Centos 7.X WordPress博客网站详细教程 FTP/PHP/mysql/Apache环境构建

此教程适用于服务器系统为centos 7.x&#xff0c;php安装版本为7.4&#xff0c;mysql安装本部为5.7. 一、mysql安装 1.1 安装三个工具 yum install wget yum install vim yum install unzip 1.2 下载并安装msql 在线下载安装包&#xff1a; wget https://dev.mysql.com/g…

JZS-7/221静态可调延时中间继电器 JOSEF约瑟

JZS-7/2系列静态可调延时中间继电器品牌&#xff1a;JOSEF约瑟型号&#xff1a;JZS-7/2名称&#xff1a;静态可调延时中间继电器额定电压&#xff1a;48380V触点容量&#xff1a;10A/250V返回系数&#xff1a;≤15%延时范围&#xff1a;15ms3s15ms5s15ms10s JZS-7/2系列静态可…