【C++从0到王者】第二十九站:二叉搜索树常见题

文章目录

  • 一、根据二叉树创建字符串
  • 二、二叉树的最近公共祖先
    • 1.解法一:递归
    • 2.解法二:借助栈来寻找路径
  • 三、二叉搜索树与双向链表
  • 四、前序与中序构建二叉树
  • 五、中序与后序构建二叉树

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

题目链接:力扣第606题:根据二叉树创建字符串

解析:

这道题主要是关于二叉树的前序遍历,题目要求我们的每一个子树都要用括号括起来,然后要求最终要删除重复的括号,那么我们先来解决最基本的,将括号都括起来再说,然后再讨论如何去除多余括号

我们很容易想到如下的方法可以先将每个都带上括号。就是一个先序遍历,需要注意的就是to_string这个函数也许不是那么的常见,它的作用是将一个整型转换为string类型的。

image-20230910220602630

我们先观察一下测试结果,跟我们预期是一样的。现在主要关心的是除去多余的括号

image-20230910220759688

对于除去多余的括号,我们可以先讨论一下,何时加上括号?根据原题的样例,我们不难得知

  1. 如果左子树不为空且右子树为空,可以省略右子树的括号,只要输出左子树的括号
  2. 如果左子树为空且右子树不为空,不可以省略左子树的括号,左右都要输出
  3. 如果两个子树都为空,两个括号都要省略,都不输出左右子树的括号
  4. 如果两个都不为空,两个都不省略,左右都要输出

上面的四条规则,根据我们现有的代码,我们可以考虑何时不省略括号的条件即可,只要当不省略的条件满足,就正常加入括号即可。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {if(root==nullptr){return "";}string str;str+=to_string(root->val);if((root->left&&root->right==nullptr)||(root->left&&root->right)||(root->left==nullptr&&root->right)){str+='(';str+=tree2str(root->left);str+=')';}if((root->left==nullptr&&root->right)||(root->left&&root->right)){str+='(';str+=tree2str(root->right);str+=')';}return str;}
};

上面的代码是可以通过的,不过上面的条件显得过于臃肿了,我们不妨简化一下

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {if(root==nullptr){return "";}string str;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;}
};

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

力扣链接:力扣第236题:二叉树的最近公共祖先

解析:

对于这道题,如果这道题是一个三叉链的话,就很简单了,因为就相当于退化为了两个链表的相交问题,此时我们直接计算长度,然后使用快慢指针即可。这样就很简单了。不过可惜的是这道题是二叉链的。所以上面的想法是不可以的。

1.解法一:递归

这道题,我们先来画图熟悉一下公共祖先的几种可能性

image-20230912165029176

如上图所示,所有的可能性就两种,一种是如果是4和6的情况下,他们所处的路径上,最近的就是5,此时我们会注意到,对于5这个结点而言,一个在左边,一个在右边。如果是更远一点的公共祖先的话,就不满足这种情况了。要么都在祖先的左边,要么都在祖先的右边。通过这一点,我们就知道了一种祖先的判别方式。还有一种情况就是一个结点是另外一个结点的祖先,如由上图所示。这时候满足的条件就是,如果此时root就等于p或者q的话,那么root就是我们的公共祖先了。

依据上面的分析,我们可以写出如下代码

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool FindNode(TreeNode* root, TreeNode* obj){if(root==nullptr){return false;}if(root == obj){return true;}return FindNode(root->left,obj)||FindNode(root->right,obj);}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 = FindNode(root->left, p);pInRight = !pInLeft;qInLeft = FindNode(root->left ,q);qInRight = !qInLeft;if(pInLeft&&qInLeft){return lowestCommonAncestor(root->left,p,q);}else if(pInRight&&qInRight){return lowestCommonAncestor(root->right,p,q);}else {return root;}}
};

时间复杂度分析:

这道题的时间复杂度是O(N²),因为最极端的情况如下所示,即二叉树已经退化为了一个链表,这时候,二叉树高度为N,且由于每次都在一边,这需要N次。在每一次中,又需要判断是不是在左树,这个判断是一个等差数列。所以最终的时间复杂度为O(N²)。不过主要的原因还是在于这是一颗普通的树,假如这棵树是一棵二叉搜索树,那么我们的代价就变小了很多。我们Find的代价直接就没有了,因为根据性质我们就可以知道某个结点是在左子树还是右子树。所以效率变为了O(N)

image-20230912170944731

2.解法二:借助栈来寻找路径

我们一开始想的办法是如果是三叉链的话,那么就可以转化为链表相交的问题,那么如果不是三叉链的话有没有办法能实现类似的思路呢?答案是有的,我们三叉链相比二叉链的优势就是三叉链可以很方便的找到路径。然后根据路径遇到相同的就是最近公共祖先了。

而对于二叉链,我们就只能使用栈来寻找路径了。只要有了路径,那么就可以采用类似的思路了。

所以我们就可以写出如下代码了

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool FindPath(TreeNode* root, TreeNode* obj, stack<TreeNode*>& st){if(root == nullptr){return false;}st.push(root);        if(obj == root){return true;}if(FindPath(root->left,obj,st)){return true;}if(FindPath(root->right,obj,st)){return true;}st.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> pst;FindPath(root,p,pst);stack<TreeNode*> qst;FindPath(root,q,qst);while(pst.size()>qst.size()){pst.pop();}while(pst.size()<qst.size()){qst.pop();}while(pst.top()!=qst.top()){pst.pop();qst.pop();}return pst.top();}
};

在这段代码中,尤其是寻找路径这段代码,是非常巧妙的,它是利用了返回值的真假来进行截断的。否则我们很难控制路径的结束。

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

牛客链接:牛客:二叉搜索树转化为双向链表

解析:

这道题我们最容易想到的办法就是暴力法,即直接用一个栈,将每一个结点都放到栈里面。然后再依次将每个结点链接起来即可。不过很可惜,这道题具有空间复杂度要求。显然不想让我们使用任何容器,想让我们就在原地进行修改链接关系。

/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/
class Solution {
public:void InOrder(TreeNode* root, TreeNode*& prev){if(root == nullptr){return;}InOrder(root->left,prev);root->left = prev;if(prev){prev->right = root; }prev = root;InOrder(root->right,prev);}TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree==nullptr){return nullptr;}TreeNode* ret = pRootOfTree;while(ret->left){ret = ret->left;}TreeNode* prev = nullptr;InOrder(pRootOfTree,prev);return ret;}
};

如上代码所示,既然要是排序的双向链表,那么必然是中序的。在中序的过程中,我们需要改变链接关系,只传一个指针是肯定不够的,虽然我们是无法窥测未来的,但是过去我们是知道的,所以我们应该传一个前驱节点,然后让当前的左指向过去,过去的右指向现在。然后将过去的值赋为现在,就可以继续下一次递归了。

四、前序与中序构建二叉树

力扣链接:力扣第105题:前序与中序构建二叉树

解析:

这道题的思路我们还是比较清楚的,根据前序确定根,然后再中序中确定根所处的位置,分割两个区间,然后递归即可。

但是再一些细节把握上还是需要注意的,递归戒截止的条件应该是当左右不构成区间了那么递归就该结束了。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* build(vector<int>& preorder,vector<int>& inorder,int& previ,int inbegin,int inend){if(inbegin>inend){return nullptr;}TreeNode* newnode = new TreeNode(preorder[previ]);int inpos = inbegin;while(inpos<=inend){if(preorder[previ]==inorder[inpos]){break;}inpos++;}previ++;newnode->left = build(preorder,inorder,previ,inbegin,inpos-1);newnode->right = build(preorder,inorder,previ,inpos+1,inend);return newnode;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int previ = 0;return build(preorder,inorder,previ,0,inorder.size()-1);}
};

五、中序与后序构建二叉树

力扣链接:力扣第106题:中序与后序构建二叉树

解析:

这道题与前面的题基本上是一样的。后序与前序的不同就是我们创建好结点以后,就得先创建右子树了,才能创建左子树。这是因为后序是左子树,右子树,根这种遍历方式的,那么越靠后就先是右子树。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* build(vector<int>& inorder,vector<int>& postorder,int& posti,int inbegin,int inend){if(inbegin>inend){return nullptr;}TreeNode* newnode = new TreeNode(postorder[posti]);int inpos = inbegin;while(inpos<inend){if(postorder[posti]==inorder[inpos]){break;}inpos++;}posti--;newnode->right = build(inorder,postorder,posti,inpos+1,inend);newnode->left = build(inorder,postorder,posti,inbegin,inpos-1);return newnode;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int posti = postorder.size()-1;return build(inorder,postorder,posti,0,inorder.size()-1);}
};

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

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

相关文章

Trinitycore学习之在Linux环境上搭建服务器并测试运行

1&#xff1a;准备环境&#xff0c;这里用ubuntu 22.04进行测试&#xff0c;安装环境后为了方便&#xff0c;换源。 注意&#xff1a;这里用的虚拟机&#xff0c;在虚拟机上生成地图信息&#xff0c;地图信息占用内存比较大&#xff0c;我暂时设置磁盘50G进行测试&#xff0c;…

Git学习记录

Contest 一、工作区域二、操作命令2.1 创建仓库2.2 查看仓库状态2.3 从工作区向暂存区添加文件2.3.1 只添加一个文件2.3.2 添加全部文件 2.4 从暂存区向仓库区添加文件2.5 查询日志2.5.1 从当前版本开始查询2.5.2 查看所有日志 2.6 回滚2.6.1 从仓库回滚到工作区2.6.2 取消工作…

c++的this指针

目录 this指针的工作原理 this指针的应用 const修饰的成员函数 this指针的工作原理 在c中同一个类的不同对象&#xff0c;在内存中有不同的储存空间&#xff0c;但是成员函数在内存中只保存了一份&#xff0c;在调用函数处理成员数据时&#xff0c;this指针能保证该成员函数…

2023大数据面试总结

文章目录 Flink&#xff08;SQL相关后面专题补充&#xff09;1. 把状态后端从FileSystem改为RocksDB后&#xff0c;Flink任务状态存储会发生哪些变化&#xff1f;2. Flink SQL API State TTL 的过期机制是 onCreateAndUpdate 还是 onReadAndWrite&#xff1f;3. watermark 到底…

《Graph of Thoughts: Solving Elaborate Problems with Large Language Models》中文翻译

《Graph of Thoughts: Solving Elaborate Problems with Large Language Models》- 思维图&#xff1a;用大型语言模型解决复杂的问题 论文信息摘要1. 介绍2. 背景及符号2.1 语言模型和情境学习 3. GoT 框架3.1 推理过程3.2 思想转变3.3 评分和排名思路 4. 系统架构及扩展性4.1…

驱动开发--自动创建节点udev机制的实现过程分析

一&#xff0c;创建设备文件的机制 1&#xff0c;mknod&#xff1a;手动创建设备节点 2&#xff0c;devfs&#xff1a;创建设备节点的逻辑在内核空间&#xff0c;2.5版本后淘汰 3&#xff0c;udev&#xff1a;自动创建设备节点的机制&#xff0c;逻辑在用户空间&#xff0c;…

vue2中使用富文本编辑器tinyMCE全过程

第一步&#xff1a;安装TinyMCE $npm install tinymce5.10.0 -S $npm install tinymce/tinymce-vue3.0.1 -S 第二步&#xff1a;在node_modules中找到tinymce文件夹将内部文件移入pubilc/tinymce文件夹中在index.html文件中引入tinymce.min.js 注意&#xff1a;不把js文件放…

数据结构和算法(5):二叉树

树 向量允许通过下标或秩&#xff0c;在常数的时间内找到目标对象&#xff1b;然而&#xff0c;一旦需要对这类结构进行修改&#xff0c;那么无论是插入还是删除&#xff0c;都需要耗费线性的时间。 列表允许借助引用或位置对象&#xff0c;在常数的时间内插入或删除元素&…

linux 系统资源命令

系统资源命令 当前系统负载 w查看的是整体的负载&#xff0c;可以来观察当前系统有没有压力 w 系统当前负载 第一行显示的内容依次为&#xff1a;时间、系统运行时间、登录用户数、平均负载(1分钟、5分钟、15分钟) 第二行后展示的信息为&#xff1a;当前登录的都有哪些用户、在…

Falcon 180B 目前最强大的开源模型

Technology Innovation Institute最近发布了Falcon 180B大型语言模型(LLM)&#xff0c;它击败了Llama-2 70b&#xff0c;与谷歌Bard的基础模型PaLM-2 Large不相上下。 180B是是Falcon 40B模型一个最新版本。以下是该模型的快速概述: 180B参数模型&#xff0c;两个版本(base和…

【Java基础篇 | 类和对象】--- 聊聊什么是内部类

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习Java的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 前言 当一个事物的内部&…

TCP服务器使用多路复用

启用复用的作用&#xff1f; 解决linux系统中的io阻塞问题&#xff0c;让多个阻塞io接口可以一起执行。无需开启线程&#xff0c;节省系统资源。 linux系统中的阻塞io有哪些&#xff1f; scanf、read管道、eadTcp套接字、acppet接收连接请求 有以下两种方式实现多路复用&am…