进阶了解C++(6)——二叉树OJ题

Leetcode.606.根据二叉树创建字符串:

606. 根据二叉树创建字符串 - 力扣(LeetCode)

难度不大,根据题目的描述,首先对二叉树进行一次前序遍历,即:

class Solution {
public:string tree2str(TreeNode* root) {if(root == nullptr){return "";}string ret = to_string(root->val);ret += tree2str(root->left);ret += tree2str(root->right);return ret;}
};

输出结果如下:

       从结果看到,元素输出的顺序正确,但是输出的格式不正确。对于题目要求的格式输出,不难发现,当一个结点的左子树的结点为空时,括号不输出。当一个结点的左子树的结点为空,但是右子树的结点不为空时,正常打印左,右结点的括号。只有当右结点存在时,才打印右结点的括号。

      对于或逻辑运算符'\left | \right |'的运算逻辑如下:a\left | \right |b\left | \right |c\left | \right |d假如a为真,则不在对下面的情况进行判断,假如a为假,则会挨个事件进行判断,如果a,b,c,d全为假才为假。在上面说到,对于一个结点的左子树根结点括号是否需要打印要分为两种情况:

      1.结点的左子树结点为空,但是结点的右子树结点不为空,此时正常对结点的左子树结点的括号进行打印。

      2.结点的左子树结点为空,同时右子树结点为空,此时不打印任何括号。

      因此,对于上面两种结点情况的判定,可以使用\left | \right |逻辑完成。首先判断root->left是否为空,如果不为空,则正常打印左结点的括号。 如果为空,则去判断root->right,如果不为空,则正常打印左结点的括号,如果为空,则不打印左结点的括号。

      对于右结点情况的判定,如果右结点为空,则不打印括号,如果不为空,则正常打印,因此,对应代码如下:
 

class Solution {
public:string tree2str(TreeNode* root) {if(root == nullptr){return "";}string ret = to_string(root->val);if(root->left || root->right){ret+='(';ret += tree2str(root->left);ret+=')';}if(root->right){ret+='(';ret += tree2str(root->right);ret+=')';}return ret;}
};

 运行结果如下:

Leetcode.102 二叉树的层序遍历:

102. 二叉树的层序遍历 - 力扣(LeetCode)

对于本题,可以利用队列来实现,具体思路如下:

    首先利用栈,将二叉树根结点所对应的元素压入队列,即:

      由于层序遍历的特点,因此在下一步,需要将3的左右子树的结点9,20进入队列。不过此时会面临一个问题,如何判断下一层全部进入队列?对于图中的树,第二层只有两个结点,姑且可以人为创建两次入队列的操作,但是对于下面层的结点,例如结点的数量为x个,不好判断入队列的次数。但是,对于这x个结点,因为其父结点的原因。是绝对可以在x/2次数内入完队列的。所以,对于入队列的次数,可以分成x/2次数次完成,即通过其父结点来完成。

    所以,额外定义一个变量levelsize来记录本层结点,即下一层结点的父结点的数量,例如对于上图中的队列,levelsize=1

   随后,利用循环,循环levlesize次,保证子结点都能入到队列中。由于题目要求的输出方式是类似于二维数组的方式,所以需要额外创建一个vector<int>类型的变量v用于向二维数组中不断插入层序遍历到的结点。

   对于上述步骤,仅仅用文字描写不够清晰,下面将利用图片进行演示:
   首先,层序遍历每一层的结点的结果在输出时是独立的,因此,在进入循环后,首先创建一个指针frontqueue中的结点的地址进行保存,并且将这个结点出队列。即:

然后,将这个结点的数值插入到v中,即:

随后,让front的左右结点依次入队列,即:

随后,令v插入到二维数组vv中,并且由于此时队列中存储了两个结点,所以改变levelsize,即:

至此,一次循环运行完毕。由于第一层的levelsize==1,因此上述步骤只会进行一次。为了便于理解,再给出第二次循环的相关图片解释。

此时队列中存储了9,20两个结点的地址。首先利用front结点保存队列中第一个结点的地址。即:

然后让队列头部元素出队列,并且将这个元素插入到v中,即:

随后,让front的左右结点依次进队列,即:

由于levelsize==2,因此,上述步骤还会进行一次,第二次具体如下:
利用front保存队列的头部元素,然后出队列的头部元素,再令v保存front->val即:

然后在令front左右结点的地址进入队列,即:

最后,令vv保存v,再改变levelsize,即:

对应代码如下:
 

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> vv;//用于进出每一次的结点queue<TreeNode*> q;//记录每层结点数量int levelsize = 0;if(root){q.push(root);levelsize = 1;}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);}levelsize = q.size();vv.push_back(v);}return vv;}
};

在上面的代码中,需要注意,由于v保存的是某一层的结点,与上层无关,因此,v定义在第一层while循环中,可以保证,每次循环执行完毕后,v都会进行一次自动的更新。

运行结果如下:

Leetcode.236 二叉树的最近公共祖先:

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

本题可以说是本文中难度最大题目。对于题目中公共祖先的定义,可以分为下面的类型:
例如对于下面给出的二叉树中,结点7,0的最近公共祖先为根结点,即存储值为3的结点:

对于结点6,4的最近公共祖先为存储值为5的结点,即:

对于结点2,4的最近公共祖先是存储值为2的结点,即:

通过对于上面不同结点所对应的公共结点,可以得到寻找公共结点的规律:

1.如果给定的两个结点p,q分别在树的左子树和右子树,则根结点root就是最近公共结点。(对应第一个图所对应的情况)。

2.如果给定的两个结点p,q同时在树的左子树或者同时在树的右子树,(对应第二个图所对应的情况),此时,可以去判断p,q是否在根结点的子结点的左右,如果在,则根结点的子结点就是p,q的最近公共祖先,即:

3.如果p,q中的一个是另一个的子结点,例如假设pq的子结点,则q就是最近公共结点。

因此,在查找p,q的最近公共祖先之前,需要先确定p,q两个结点在树中的位置。二者的位置可以分为下面四种情况: p结点在左子树,p结点在右子树,q结点在左子树,q结点在右子树。为了方便表示,用pInleft,pInright,qInleft,qInright分别表示上面的四种情况。对于上面结点位置的查询,可以使用递归来实现,具体代码如下:

bool IsInTree(TreeNode* root, TreeNode* x){if(root == nullptr){return false;}return root == x || IsInTree(root->left,x) || IsInTree(root->right,x);}

同时,如果题目给定的树为空树,则不需要进行判断,如果给定的p,q其中之一就是根结点,则直接返回根结点即可。

在利用上面的代码确定了p,q结点在树中的位置后,需要分下面的情况进行判定:
入如果pInleft,qInright同时为真或者pInright,qInleft同时为真,则说明p,q结点分别分布在左子树或者右子树。因此对于这种情况,直接返回根结点即可。

如果pInleft,qInleft同时为真或者pInright,qInright同时为真,则说明p,q同时在左子树或者右子树。因此直接对其根结点左子树,或者右子树进行重复判断即可。对应代码如下:
 

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(root == p || root == q){return root;}bool pInleft,pInright,qInleft,qInright;pInleft = IsInTree(root->left,p);pInright = ! pInleft;qInleft = IsInTree(root->left,q);qInright = ! qInleft;if((pInleft && qInright) || (pInright && qInleft)){return root;}else if((pInleft && qInleft) ){return lowestCommonAncestor(root->left,p,q);}else if((qInright && pInright)){return lowestCommonAncestor(root->right,p,q);}assert(false);return nullptr;}
};

运行结果如下:

但是这种方法时间复杂度太大。因此,可以采用下面的方法来优化时间复杂度:

例如对于上面的树,如果将查找两个结点的路径记录,则记录的结点如下:

则不难发现,所谓的最近公共祖先,就是二者路径上最近的相同结点。而重点,就是如何去记录这个路径。文章给出一种方法,具体如下:
首先将寻找路径的函数命名为GetPath,首先检测当前结点是否为空,如果为空,则直接返回false,如果不为空,首先创建一个栈,这里将这个栈命名为s,让结点入栈。在入栈后,检测当前结点是否是需要寻找的结点,如果是则返回true。如果不是,则分别递归结点的左右子树。如果在左右子树中没有找到,则出栈一次。为了方便理解,下面用图来演示这一过程:

首先,此时的结点3不为空,因此直接入栈,即:

由于3并不是需要找的结点,因此先去结点的左子树中进行寻找,即5。与上方相同的逻辑,首先让5入栈,即:

由于5也不是需要查找的结点,因此按照递归,再去左子树进行查找。

让结点6入栈,即:

由于6也不是需要找的结点,因此继续往左子树递归。由于6结点的左,右子树结点都为空,因此判定为左,右子树都找不到结点,所以将6弹出栈,即:

此时递归回到上一层,由于5结点的左子树找不到需要的结点,因此去其右子树进行查找,即将2入栈;


同时,由于2也不是需要找的结点,因此去其左子树进行查找,即将7入栈。由于此时的结点为需要查找的结点,因此返回true。此时栈中内容如下;

此时,便获取了查找结点的路径。

对于另一个需要查找的结点,由于原理相同,因此不再过多叙述。

在得到了两个结点的路径后,首相让长的路径逐渐pop,直到两个栈的size相同即可。随后挨个比较栈中元素。如果相同则返回即可。对应代码如下:
 

class Solution {
public:bool GetPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& s){if(root == nullptr){return false;}s.push(root);if(root == x){return true;}if(GetPath(root->left,x,s)){return true;}if(GetPath(root->right,x,s)){return true;}s.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> sp;stack<TreeNode*> sq;GetPath(root,p,sp);GetPath(root,q,sq);while(sp.size() != sq.size()){if(sp.size() > sq.size()){sp.pop();}else{sq.pop();}}while(sp.top() != sq.top()){sp.pop();sq.pop();}return sp.top();}
};

运行结果如下:

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

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

相关文章

jmeter总结之:Regular Expression Extractor元件

Regular Expression Extractor是一个后处理器元件&#xff0c;使用正则从服务器的响应中提取数据&#xff0c;并将这些数据保存到JMeter变量中&#xff0c;以便在后续的请求或断言中使用。在处理动态数据或验证响应中的特定信息时很有用。 添加Regular Expression Extractor元…

Capture One Pro 22 for Mac/win:重塑RAW图像处理的艺术

在数字摄影的世界里&#xff0c;RAW图像处理软件无疑是摄影师们手中的魔法棒&#xff0c;而Capture One Pro 22无疑是这一领域的璀璨明星。这款专为Mac和Windows系统打造的图像处理软件&#xff0c;以其出色的性能、丰富的功能和极致的用户体验&#xff0c;赢得了全球摄影师的广…

数据库原理与应用(SQL Server)笔记 关系数据库

目录 一、关系数据库的基本概念&#xff08;一&#xff09;关系数据库的定义&#xff08;二&#xff09;基本表、视图&#xff08;三&#xff09;元组、属性、域&#xff08;四&#xff09;候选码、主码、外码 二、关系模型三、关系的完整性&#xff08;一&#xff09;实体完整…

010——服务器开发环境搭建及开发方法(下)

目录 三、 第一个驱动程序 四、 buildroot 4.1 制作根文件系统 4.2 buildroot使用 五、 uboot 009——服务器开发环境搭建及开发方法&#xff08;上&#xff09;-CSDN博客 三、 第一个驱动程序 # 1. 使用不同的开发板内核时, 一定要修改KERN_DIR # 2. KERN_DIR中的内核要…

百度智能云推出AI大模型全家桶;抖音发布 AI 生成虚拟人物治理公告

百度智能云推出大模型全家桶 百度智能云昨日在北京首钢园召开「Al Cloud Day: 大模型应用产品发布会」&#xff0c;此次发布会上&#xff0c;百度智能云宣布对以下 7 款产品进行升级。 数字人平台百度智能云曦灵智能客服平台百度智能云客悦内容创作平台「一念」知识智平台「甄…

【计算机网络】第 11、12 问:流量控制和可靠传输机制有哪些?

目录 正文流量控制的基本方法停止-等待流量控制基本原理滑动窗口流量控制基本原理 可靠传输机制1. 停止-等待协议2. 后退 N 帧协议&#xff08;GBN&#xff09;3. 选择重传协议&#xff08;SR&#xff09; 正文 流量控制涉及对链路上的帧的发送速率的控制&#xff0c;以使接收…

HTTP

HTTP 概念&#xff1a;HyperTextTransferProtocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则 HTTP协议特点&#xff1a; 1.基于TCP协议&#xff1a;面向连接&#xff0c;安全 2.基于请求-响应模型的&#xff1a;一次请求对应一次响应 …

区块链dapp开发 dapp系统开发方案

在区块链技术的兴起和普及的推动下&#xff0c;去中心化应用程序&#xff08;DApp&#xff09;成为了当前数字世界中的热门话题之一。DApp 的开发不仅需要考虑技术方面的挑战&#xff0c;还需要深入了解区块链的工作原理和应用场景。本文将介绍一种 DApp 系统开发的基本方案&am…

CSS实现小车旅行动画实现

小车旅行动画实现 效果展示 CSS 知识点 灵活使用 background 属性下的 repeating-linear-gradient 实现路面效果灵活运用 animation 属性与 transform 实现小车和其他元素的动画效果 动画场景分析 从效果图可以看出需要实现此动画的话&#xff0c;需要position属性控制元素…

旧电脑安装个Win11玩玩,看看体验如何!

前言 小伙伴们都很清楚&#xff0c;Windows11的配置要求其实并不是很高&#xff0c;但要求的受信任平台模块&#xff08;TPM&#xff09;版本要求2.0 由于受信任平台模块的限制&#xff0c;导致许多电脑都没办法安装Windows11&#xff0c;如果要安装Windows11的旧机器也只能绕…

【QT+QGIS跨平台编译】040:【geos_c+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、geos_c介绍二、文件下载三、文件分析四、pro文件五、编译实践一、geos_c介绍 GEOS_C(GEOS C++接口)是GEOS库的C语言版本,它提供了一套丰富的API,允许开发者在C++程序中执行复杂的几何形状处理和空间关系分析。GEOS_C是基于JTS(Java Topolog…

步态采集平台

&#x1f349;步骤一、读取视频每一帧图像 &#x1f349;步骤二、对读取的图像进行分割&#xff0c;得到全景下的步态轮廓图。 ​​​​​​​&#x1f349;步骤三、对读取的图像进行裁剪得到归一化的步态轮廓图。 ​​​​​​​&#x1f349;步骤四、保存这一帧步态轮廓图

新网站收录时间是多久,新建网站多久被百度收录

对于新建的网站而言&#xff0c;被搜索引擎收录是非常重要的一步&#xff0c;它标志着网站的正式上线和对外开放。然而&#xff0c;新网站被搜索引擎收录需要一定的时间&#xff0c;而且时间长短受多种因素影响。本文将探讨新网站收录需要多长时间&#xff0c;以及新建网站多久…

SnapGene 5 for Mac 分子生物学软件

SnapGene 5 for Mac是一款专为Mac操作系统设计的分子生物学软件&#xff0c;以其强大的功能和用户友好的界面&#xff0c;为科研人员提供了高效、便捷的基因克隆和分子实验设计体验。 软件下载&#xff1a;SnapGene 5 for Mac v5.3.1中文激活版 这款软件支持DNA构建和克隆设计&…

网络:udptcp套接字

目录 协议 网络传输基本流程 网络编程套接字 udp套接字编程 udp相关代码实现 sock函数 bind函数 recvfrom函数 sendto函数 udp执行指令代码 popen函数 udp多线程版收发消息 tcp套接字编程 tcp套接字代码 listen函数 accept函数 read/write函数 connect函数 recv/…

UE RPC 外网联机(2)

外网联机配置测试 一、网络配置 开放外网端口开放端口是为了可以进行外网访问;端口包含一个预案管理服务器端口和多个预案服务器端口;(预案管理服务器类似于大厅,预案服务器类似于房间,大厅管理多个房间;) (1)预案管理服务器端口;(如:23001) (2)预案服务器端口…

C++优先队列——priority_queue,函数对象,labmda表达式,pair等

头文件&#xff1a;#include<queue> 内部使用堆来实现&#xff0c;在需要或得最大的几个值或最小的几个值而不关心整个数组的顺序时非常好用。 用法&#xff1a; priority_queue<int, vector<int>, greater<int>>q; 第一个参数为堆中存储的元素。 …

大模型 智能体 智能玩具 智能音箱 构建教程 wukong-robot

视频演示 10:27 一、背景 继上文《ChatGPT+小爱音响能擦出什么火花?》可以看出大伙对AI+硬件的结合十分感兴趣,但上文是针对市场智能音响的AI植入,底层是通过轮询拦截,算是hack兼容,虽然官方有提供开发者接口,也免不了有许多局限性(比如得通过特定指令唤醒),不利于我…

零基础10 天入门 Web3之第1天

10 天入门 Web3 Web3 是互联网的下一代&#xff0c;它将使人们拥有自己的数据并控制自己的在线体验。Web3 基于区块链技术&#xff0c;该技术为安全、透明和可信的交易提供支持。我准备做一个 10 天的学习计划&#xff0c;可帮助大家入门 Web3&#xff1a; 想要一起探讨学习的…

大模型时代下的“金融业生物识别安全挑战”机遇

作者&#xff1a;中关村科金AI安全攻防实验室 冯月 金融行业正在面临着前所未有的安全挑战&#xff0c;人脸安全事件频发&#xff0c;国家高度重视并提出警告&#xff0c;全行业每年黑产欺诈涉及资金额超过1100亿元。冰山上是安全事件&#xff0c;冰山下隐藏的是“裸奔”的技术…