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

news/2024/4/28 21:18:59/文章来源:https://blog.csdn.net/2301_76836325/article/details/137029090

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.luyixian.cn/news_show_1027530.aspx

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

相关文章

使用ai智能写作场景之gpt整理资料,如何ai智能写作整理资料

Ai智能写作助手&#xff1a;Ai智能整理资料小助手 Ai智能整理资料小助手可试用3天&#xff01; 通俗的解释一下怎么用ChatGPT来进行资料整理&#xff1a; 搜寻并获取指定数量的特定领域文章&#xff1a; 想像你在和我说话一样&#xff0c;告诉我你想要多少篇关于某个话题的文…

HarmonyOS模拟器调试

1 、设置 -> 系统设置 -> 关于手机 快速点击 5 次 HarmonyOS 版本开启开发者模式。 2 、设置 -> 系统和更新 -> 开发人员选项 到开发人员选项后往下拉有 USB 调试 &#xff0c;把 USB 调试开关打开。 源自&#xff1a;HarmonyOS HarmonyOS Next 仿小米商城App入门…

【FedCoin: A Peer-to-Peer Payment System for Federated Learning】

在这篇论文中&#xff0c;我们提出了FedCoin&#xff0c;一个基于区块链的点对点支付系统&#xff0c;专为联邦学习设计&#xff0c;以实现基于Shapley值的实际利润分配。在FedCoin系统中&#xff0c;区块链共识实体负责计算SV&#xff0c;并且新的区块是基于“Shapley证明”&a…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之九 简单闪烁效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之九 简单闪烁效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之九 简单闪烁效果 一、简单介绍 二、简单闪烁效果实现原理 三、简单闪烁效果案例实现简单步骤 四、注意事项 一、简单…

DevSecOps平台架构系列-微软云Azure DevSecOps平台架构

目录 一、概述 二、Azure DevOps和黄金管道 2.1 概述 2.2 Azure DevOps架构说明 2.2.1 架构及管道流程图 2.2.2 架构内容 2.2.2.1 Azure Boards 2.2.2.2 Azure Repos 2.2.2.3 Azure Test Plans 2.2.2.4 Azure Pipelines 2.2.2.5 Azure Application Insights 2.2.2.6…

蓝桥杯_day6

文章目录 不同路径不同路径II拿金币珠宝的最高价值 不同路径 【题目描述】 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为…

Linux中的vim/vi编辑器

VI 是 Unix 操作系统和类 Unix 操作系统中最通用的文本编辑器。 VIM 编辑器是从 VI 发展出来的一个性能更强大的文本编辑器&#xff0c;可以说是&#xff1a;编辑器之神。可以主动的以字体颜 色辨别语法的正确性&#xff0c;方便程序设计。VIM 与 VI 编辑器完全兼容。 一:三种…

【JS球球大作战项目实战】+在线体验

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

论文《Exploring to Prompt for Vision-Language Models》阅读

论文《Exploring to Prompt for Vision-Language Models》阅读 论文概况论文动机&#xff08;Intro&#xff09;MethodologyPreliminaryCoOp[CLASS]位置Context 是否跨 class 共享表示和训练 ExperimentsOverall ComparisonDomain GeneralizationContext Length (M) 和 backbon…

ChatGPT 商业金矿(上)

原文&#xff1a;ChatGPT Business Goldmines 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第一章&#xff1a;为什么我写这本书 欢迎阅读《ChatGPT 多源收入&#xff1a;20 个利润丰厚的业务&#xff0c;任何人都可以在一周内使用 ChatGPT 开始》。我很高兴分享我…

Backend - gitea 首次建库(远端本地)

目录 一、建立远端储存库 1. 进入新增画面 2. 填写储存库名称&#xff08;如book&#xff09;&#xff0c;点击“建立”即可 二、本地关联远端储存库 1. 本地初始化储存库代码 &#xff08;1&#xff09;新建文件夹 &#xff08;2&#xff09;获取远端储存库 2. 本地编写…

冒泡排序(六大排序)

冒泡排序 冒泡排序的特性总结&#xff1a; 1. 冒泡排序是一种非常容易理解的排序 2. 时间复杂度&#xff1a;O(N^2) 3. 空间复杂度&#xff1a;O(1) 4. 稳定性&#xff1a;稳定 动图分析&#xff1a; 代码实现&#xff1a; Swap(int*p1,int*p2) {int tmp *p1;*p1*p2…

Xcode 15 Sandbox: rsync(xxxx) deny(1) file-write-create

设置里面搜索user 把User Script Sanboxing 改为NO 新版本的Xcode 15 编译报该错误 右侧工具栏 项目的workspace 和 pod的 space 都选择为15.0 即可

Springboot整合瀚高

需要下载highgo驱动,然后将jar包打入进自己本地maven中 下载地址: highgi6.2.4 1.打开jar包所在的文件&#xff0c;然后在该文件夹中打开命令窗口&#xff08;或者先打开命令窗口&#xff0c;然后cd到jar所在文件夹&#xff09; install-file -Dfile&#xff1a;jar包名Dart…

想学网络安全,从哪里开始?网络安全的学习路线

网络安全学习路线&#xff1a; 想学习网络安全专业的知识&#xff0c;想当黑客&#xff0c;但是不知道该从哪里开始学。 我给你一个路线&#xff01; 清晰图片和大纲&#xff1a;https://docs.qq.com/doc/DU1lpVFpSbWVrd2p3

.NET分布式Orleans - 2 - Grain的通信原理与定义

Grain 是 Orleans 框架中的基本单元&#xff0c;代表了应用程序中的一个实体或者一个计算单元。 每个Silo都是一个独立的进程&#xff0c;Silo负责加载、管理和执行Grain实例&#xff0c;并处理来自客户端的请求以及与其他Silo之间的通信。 通信原理 在相同的Silo中&#xff0…

SCI论文改写、防查重神器QuillBot如何付费高级版本?

写论文时候的修改软件QuillBot&#xff0c;正常的文献里的句子帖进去&#xff0c;直接给各种倒装和各种同义词替换至少10次&#xff0c;保证查不出来是别人的句子。 QuillBot是一个帮助改写内容的转述工具。 Quillbot让你的内容重组变得简单。 转述是指你用不同的词来表达&a…

短视频账号矩阵系统/开发 -- -- -- 路径积ai算法上线

短视频账号矩阵系统&#xff0c;短视频矩阵系统开发3年技术之路&#xff0c;目前已经在技术竞品出沉淀出来&#xff0c;近期技术迭代的新的功能同步喽&#xff1a; php7.4版本&#xff0c;自研框架&#xff0c;有开发文档&#xff0c;类laravel框架 近期剪辑迭代的技术算法&am…

【HTTP完全注解】一些神奇的URL

URL HTTP 请求的内容被称为"资源"&#xff0c;‘资源’这一概念非常宽泛&#xff0c;它可以是一份文档&#xff0c;一张图片&#xff0c;或所有其他你能够想到的格式。每个资源的名称和位置由一个 URL&#xff08;统一资源定位符&#xff0c;它是 URI 的一种&#x…

【计算机网络】http协议的原理与应用,https是如何保证安全传输的

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…