【二叉树经典题目】

根据二叉树创建字符串

本题的关键在于什么情况要省略括号,什么情况不能省略:

  1.  左右为空可以省略括号
  2.  左不为空,右为空可以省略括号
  3. 左为空,右不为空不能省略括号
class Solution {
public://1.左右为空可以省略括号//2.左不为空,右为空可以省略括号//3.左为空,右不为空不能省略括号string tree2str(TreeNode* root) {string ret;if (root == nullptr){return ret;}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;}
};

 

二叉树的层序遍历

层序遍历咋一看很简单,借助队列就能实现,但是关键在于如何把每一层的结点区分开来,放到不同的vector中。这里采用的方法是使用一个计数器count来记录本层的结点数,count减到0时,本层结点已经遍历完了,此时队列中的就是下一层的结点,用队列的元素个数更新count即可。 

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> ret;if (root == nullptr){return ret;}queue<TreeNode*> q;q.push(root);int count = 1;//记录本层的结点数while (!q.empty()){vector<int> v;while (count--){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);}}ret.push_back(v);count = q.size();}return ret;}
};

 

 二叉树的最近公共祖先

思路:找到根结点到p结点和q结点的路径,对比两个路径即可得到答案,时间复杂度O(n)

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {vector<TreeNode*> pPath;vector<TreeNode*> qPath;findPath(root, p, pPath);findPath(root, q, qPath);int minSize = min(pPath.size(), qPath.size());int i = 0;for (; i < minSize; i++){if (pPath[i] != qPath[i]){break;}}return pPath[i - 1];}bool findPath(TreeNode* root, TreeNode* target, vector<TreeNode*>& path){if (root == nullptr){return false;}path.push_back(root);if (root == target){return true;}if (findPath(root->left, target, path)){return true;}if (findPath(root->right, target, path)){return true;}//回溯--恢复现场path.pop_back();return false;}
};

二叉搜索树转双向链表

要将二叉搜索树转换为有序的双向链表,那么肯定与中序遍历有关。中序遍历过程中用prev记录上一个遍历的结点,cur记录当前结点,prev->right = cur, cur->left = prev即可。

class Solution {
public:void InOrderConvert(TreeNode* cur, TreeNode*& prev){if (cur == nullptr){return;}InOrderConvert(cur->left, prev);cur->left = prev;if (prev){prev->right = cur;}prev = cur;InOrderConvert(cur->right, prev);}TreeNode* Convert(TreeNode* pcurOfTree) {TreeNode* ret = pcurOfTree;if (ret == nullptr){return ret;}//找到最小的结点--也就是重新链接后双链表的头结点while (ret->left){ret = ret->left;}TreeNode* prev = nullptr;InOrderConvert(pcurOfTree, prev);return ret;}
};

根据前序序列与中序序列构建二叉树

手工构建二叉树很容易,本题难点在于如何将手动构建的过程转化成代码。想一想手工构建的过程。如何确定二叉树的根结点?看前序序列。如何确定左右子树有哪些结点?看中序序列。如何构建左右子树呢? 先确定根节点,这样又转化成了第一个问题。

牢记这一点:前序序列确定根结点。,中序序列确定左右子树有哪些结点。怎么表示左右子树的结点?用一个区间标识范围即可。

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]);prei++;//根据中序序列确定左右子树的结点int pos = inbegin;while (pos <= inend) {if (root->val == inorder[pos]){break;}pos++;}//[inbegin, pos - 1] [pos + 1, inend]root->left = _buildTree(preorder, inorder, prei, inbegin, pos - 1);root->right = _buildTree(preorder, inorder, prei, pos + 1, inend);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int prei = 0;return _buildTree(preorder, inorder, prei, 0, inorder.size()- 1);}
};

根据后序序列和中序序列构建二叉树

思路和上一题类似,不过要注意postorder要从后往前遍历,并且要先构建右子树,再构建左子树。 

class Solution {
public:TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int posti = postorder.size() - 1;return _buildTree(inorder, postorder, posti, 0, inorder.size() - 1);}TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder, int& posti, int inbegin, int inend){if (inbegin > inend){return nullptr;}//后序序列确定根节点TreeNode* root = new TreeNode(postorder[posti]);posti--;//中序序列确定左右子树int pos = inbegin;while (pos <= inend){if (root->val == inorder[pos]){break;}pos++;}//[inbegin, pos - 1] [pos + 1, inend]//构建右子树和左子树root->right = _buildTree(inorder, postorder, posti, pos + 1, inend);root->left = _buildTree(inorder, postorder, posti, inbegin, pos - 1);return root;}
};

二叉树的前序遍历迭代版本

 递归写法很简单,如何用迭代来完成呢?

将一颗树分为左路结点和左路结点的右子树,遍历到一颗树的左路结点时就访问它,并将它放入栈中,继续访问下一个左路结点。当左路结点访问完后,从栈中取结点,该结点就是最近的一个左路结点,接下来访问它的右子树,又回到了最开始的步骤。

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> ret;TreeNode* cur = root;while (cur || !st.empty()){//遍历左路结点并访问while (cur){ret.push_back(cur->val);st.push(cur);cur = cur->left;}//遍历左路结点的右子树TreeNode* top = st.top();cur = top->right;st.pop();}return ret;}
};

二叉树的中序遍历迭代版本

和上一题类似,一颗树看作左路结点和左路结点的右子树,和前序遍历不同的是,结点的访问时机不同。前序遍历是遍历到某个结点时就要访问它,而中序遍历,遍历到某个结点时先不访问,将它存入栈中,当再把它从栈中取出来时,代表它的左子树已经访问过了,接下来要访问右子树,此时就是访问该结点的时机。

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> ret;TreeNode* cur = root;while (cur || !st.empty()){//遍历左路结点while (cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();st.pop();//访问左路结点ret.push_back(top->val);//遍历左路结点的右子树cur = top->right;}return ret;}
};

二叉树的后序遍历迭代版本

同样,一棵树分为左路结点和左路结点的右子树。左路结点的访问访问时机和前两道题又有所不同。遍历到左路结点时将它存入栈中,不能访问,当它的左子树遍历完时,从栈中取出,此时还是不能访问,要取遍历它的右子树,访问完右子树后,再次从栈中取出,此时才能访问并出栈。

本题难点在于,当你把结点从栈中取出来时,你如何知道它的右子树是否已经访问了呢?此时应该访问这个结点,还是暂时不访问,去遍历它的右子树呢?有两种方案:

1.设置flag值标记结点的右子树是否已经访问。

要让每一个结点都对应一个flag值,怎么做到呢?很简单,再建立一个栈,类型为bool,当结点入栈时,bool类型的变量也跟着入栈(注意不是同一个栈),初始值为false。当从栈中取出结点时,也把对应的bool变量取出,如果为false,说明右子树还没有遍历,将bool变量置为true,访问右子树;若为true,说明右子树已经遍历,访问结点并出栈,同时将bool变量也出栈。结点入栈,bool变量入栈;结点出栈,bool变量也出栈,这样一来就能做到一一对应了。

lass Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;stack<bool> flag;vector<int> ret;TreeNode* cur = root;while (cur || !st.empty()){//遍历左路结点while (cur){st.push(cur);flag.push(false);cur = cur->left;}TreeNode* top = st.top();if (top->right == nullptr || flag.top() == true){//访问左路结点ret.push_back(top->val);st.pop();flag.pop();}else{//遍历左路结点的右子树flag.top() = true;cur = top->right;}}return ret;}
};

2.标记前一个访问的结点

如果一个结点的右子树 (非空) 遍历完了,那么上一个访问的结点一定是该结点的右孩子。根据这一规律,只需创建一个prev变量来记录上一个访问的结点。如果一个结点从栈中取出来,若该结点右子树为空,则可以直接访问;若该结点右子树不为空且上一个访问的结点是该结点的右孩子,则也可以访问;否则就要先访问该结点的右子树。

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> ret;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){//访问左路结点ret.push_back(top->val);st.pop();prev = top;}else{//遍历右子树cur = top->right;}}return ret;}
};

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

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

相关文章

初识HTML超文本标记语言

文章目录 前端简介引入前端三剑客什么是HTML&#xff1f;超文本传输协议前戏HTTP超文本传输协议1.什么是HTTP协议2.四大特性3.数据格式4.响应状态码 基于HTTP协议搭建HTMLHTML简介HTML文档结构head常见标签1.meta 定义网页源信息(很多配置)2.style内部支持编写CSS代码3.link引入…

SpringCloud(二) Eureka注册中心的使用

在SpringCloud(一)中,我们学会了使用RestTemplate进行远程调用,但是在调用user-service时候需要在order-service中发送http请求,请求中需要书写对应微服务的ip和端口号,十分不方便,如果此时有多个user-service实例的话,就不知道调用哪个了(除非每次调用的时候都对ip和端口号进行…

论文-分布式-并发控制-并发控制问题的解决方案

目录 参考文献 问题 解法与证明 易读版本 参考文献 Dijkstra于1965年发表文章Solution of a Problem in Concurrent Programming Control&#xff0c;引出并发系统下的互斥(mutual exclusion)问题&#xff0c;自此开辟了分布式计算领域Dijkstra在文中给出了基于共享存储原子…

sqlite3 关系型数据库语言 SQL 语言

SQL(Structured Query Language)语言是一种结构化查询语言,是一个通用的,功能强大的关系型数据库操作语言. 包含 6 个部分: 1.数据查询语言(DQL:Data Query Language) 从数据库的二维表格中查询数据,保留字 SELECT 是 DQL 中用的最多的语句 2.数据操作语言(DML) 最主要的关…

易点天下受邀参与云栖大会,以AIGC重塑出海营销新范式

10月31日&#xff0c;2023云栖大会在杭州云栖小镇拉开帷幕。与往年不同&#xff0c;今年的云栖大会以“计算&#xff0c;为了无法计算的价值”为主题&#xff0c;与国际潮流科技大会组织方式接轨&#xff0c;通过云计算、人工智能、产业创新三大主题馆40000平科技展&#xff0c…

redis缓存穿透

redis缓存穿透 模拟一个缓存穿透的环境&#xff1a; redis缓存穿透1. 准备一个GET请求并且在第一次访问的时候将数据写入缓存2. 再次访问的时候首先判断缓存是否命中3. 命中了直接返回&#xff0c;未命中重建缓存1. 缓存空对象2. 布隆过滤器 1. 准备一个GET请求并且在第一次访问…

avi怎么转mp4?

avi怎么转mp4&#xff1f;如今市面上涌现了各种多样的视频格式&#xff0c;其中AVI作为一种音频视频交错格式&#xff0c;虽然使用较少但相对常见。它的优点在于占用空间较小&#xff0c;但画面质量并不是很出色。然而&#xff0c;AVI格式也存在一个明显的缺点&#xff0c;即兼…

柯桥专升本学校,自考本科文凭的价值如何?

自考本科文凭的价值如何&#xff1f; 自考本科学历是通过独立学习和考试获得的一种本科学历。对于自考本科学历的价值&#xff0c;很多人感到困惑&#xff0c;那么究竟自考本科学历有多大的价值呢? 首先&#xff0c;在就业市场上&#xff0c;自考本科学历具有一定的竞争力。随…

VBA技术资料MF77:组合所选范围中的所有形状

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

横屏签字板手写签名并旋转90°转为横屏显示base64

手写签名并旋转90转为横屏显示base64 base64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAApsAAAF3CAYAAADq/IAAAAAAAXNSR0IArs4c6QAAIABJREFUeF7t3W3MPlldH/CvwLK7wEKNoiABZLvYiBqIdJGnQlor9gVBaAtJmzQsoI2wgKD4hqVNmrD7ohWhPFRSIgaKO0XTS8AVLb4AIVtFnaSiMPSktxoyVVWFxAF…

我的云栖大会之旅:见证云计算创新的15年

云栖大会&#xff0c;曾经是一次不可思议的科技之旅&#xff0c;却如今已见证了我对云计算世界的15年关注和发展。第一次踏上云栖大会之旅&#xff0c;我记得是在2009年。那时的云计算还是一个新生事物&#xff0c;而云栖大会正是其中的奠基石。 我清楚地记得那个炎热的夏天&am…

OpenCV标定演示,及如何生成标定板图片

标定的程序在官方的源码里有&#xff0c; opencv-4.5.5\samples\cpp\tutorial_code\calib3d\camera_calibration 很多小白不知道怎么跑起来&#xff0c;这个也怪OpenCV官方&#xff0c;工作没做完善&#xff0c;其实的default.xml是要自己手动改的&#xff0c;输入的图片也要…

【QT】鼠标常用事件

新建项目 加标签控件 当鼠标进去&#xff0c;显示【鼠标进入】&#xff0c;离开时显示【鼠标离开】 将QLable提升成自己的控件&#xff0c;然后再去捕获 添加文件 改继承的类名 提升类 同一个父类&#xff0c;可以提升 效果 现在代码就和Qlabel对应起来了。 在.h中声明&…

Linux———— 运算命令

Shell与其他编程语言一样&#xff0c;支持多种类型的运算符&#xff0c;包括&#xff1a; 算术运算符&#xff1a;用于执行数学运算&#xff0c;例如加法、减法、乘法和除法。 关系运算符&#xff1a;用于比较两个值之间的关系&#xff0c;例如相等、大于、小于等。 布尔运算…

FPGA 如何 固化程序到 FLASH中

1、导出Hardware 2、导出bit文件 3、打开SDK 4、 点击Ok 5、创建工程 6、 输入工程名称&#xff1a;guhua 7、选择 Zynq FSBL 8、单击 guhua、然后点击 build 点击&#xff1a;build all 9、 右键之后&#xff0c;点击&#xff1a;Creat Boot Image 10、点击 Cr…

项目解读_v2

1. 项目介绍 如果使用task2-1作为示例时&#xff0c; 运行process.py的过程中需要确认 process调用的是函数 preprocess_ast_wav2vec(wav, fr) 1.1 任务简介 首个开源的儿科呼吸音数据集&#xff0c; 通过邀请11位医师标注&#xff1b; 数字听诊器的采样频率和量化分辨率分…

大厂面试题-网络四元组

四元组&#xff0c;简单理解就是在TCP协议中&#xff0c;去确定一个客户端连接的组成要素&#xff0c;它包括源 IP地址、目标IP地址、源端口号、目标端口号。 正常情况下&#xff0c;我们对于网络通信的认识可能是这样(如图)。 服务端通过Server Socket建立一个对指定端口号…

商城性能测试LoadRunner快速上手教学

软件介绍 Virtual User Generator &#xff0c;记录用户流程并创建一个自动化性能测试脚本Controller&#xff0c;单一控制点&#xff0c;轻松、有效地控制所有Vuser&#xff0c;执行期间监控场景性能Analysis&#xff0c;生成性能测试报告&#xff0c;以图表形式呈现。 由于…

UE5使用Dash插件实现程序化地形场景制作

目录 0 dash下载后激活 1 初步使用 2 导入bridge的资产路径 3 练习成果 4 参考链接 0 dash下载后激活 1 初步使用 Dash插件点击蓝色的A&#xff0c;可以使用。 通过输入不同提示命令&#xff0c;来激活不同的功能。 2 导入bridge的资产路径 这里需要注意是UAsserts…