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

news2024/11/23 8:48:19

文章目录

  • 一、根据二叉树创建字符串
  • 二、二叉树的最近公共祖先
    • 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.coloradmin.cn/o/1004680.html

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

相关文章

fabic 在canvas中绘制group元素,如何获取并修改group中某一元素的填充色

1、创建一个Fabric组并向其添加元素 2、标识要修改的组中的特定元素 3、访问并修改该元素的填充颜色 下面是JavaScript中的一个代码示例: // Create a Fabric.js canvas var canvas new fabric.Canvas(canvas);// Create a rectangle and a circle var rectangle new fabric…

Linux——环境变量

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;Linux——环境变量 ☂️<3>开发环境&#xff1a;Centos7 &#x1f4ac;<4>前言&#xff1a;环境变量(environment variables)一般是指在操作系统中用来指定操作…

2023年一级建造师建设工程经济真题

2023年一级建造师建设工程经济真题 1.根据《建设工程工程量清单计价规范》规定&#xff0c;代表专业工程的项目编码是 ()。 A、1&#xff0c;2 B、3&#xff0c;4 C、5&#xff0c;6 D、7&#xff0c;8&#xff0c;9 【答案】B 2.某公司希望所投资项目在第5年末回收1000万…

银行笔试篇---职业能力测试(行测)

数字推理 数字推理可分为等差数列、等比数列、和数列、积数列、幂数列以及分数数列六类&#xff0c;做题时的总体原则为&#xff1a; 关键点1&#xff1a;凡是一次变化找不到规律的&#xff0c;直接放弃&#xff01;所谓一次变化指的是&#xff1a;1.通过一次相邻两数作差、作…

成绩定级脚本(Python)

成绩评定脚本 写一个成绩评定的python脚本&#xff0c;实现用户输入成绩&#xff0c;由脚本来为成绩评级&#xff1a; #成绩评定脚本.pyscoreinput("please input your score:") if int(score)> 90:print("A") elif int(score)> 80:print("B&…

Pytest系列- assert断言详细使用(4)

简介 在断言方面&#xff0c;pytest框架比其他类似的框架&#xff08;比如unittest&#xff09;更加简洁&#xff0c;易用&#xff0c;我想这是选择pytest作为自动化测试框架之一的原因之一。pytest的assert断言关键字支持使用python内置的assert表达式。可以理解为pytest的断…

合宙Air724UG LuatOS-Air lvgl7-lvgl(矢量字体)

如何用开发板实现lvgl加载外部矢量字体功能 目录名称 如何用开发板实现lvgl加载外部矢量字体功能 简介材料准备API 说明步骤 1. 将字库芯片接在模块spi上2. 版本定制3. 初始化spi4. 设置字体5.字体使用测试固件和脚本显示效果字号灰度最佳粗细值对应表常见问题 1. 设置68号字体…

Aapache Tomcat AJP __ 文件包含漏洞 __ CVE-2020-1938

Aapache Tomcat AJP __ 文件包含漏洞 __ CVE-2020-1938 漏洞描述 Ghostcat是Chaitin Tech安全研究员发现的Tomcat中的一个严重漏洞&#xff0c;由于Tomcat AJP协议中的缺陷&#xff0c;攻击者可以读取或包含Tomcat的Webapp目录中的任何文件。例如&#xff0c;攻击者可以读取 …

9月12日华为新品发布会

华为MATE 60 Pro作为华为Mate系列的最新力作&#xff0c;备受期待。这款手机在设计和功能上都带来突破&#xff0c;大家都认为是遥遥领先。 9月12日下午&#xff0c;大家期待的华为发布会&#xff0c;实际上是问界新M7发布会。虽然此前已有消息称&#xff0c;此次发布不包括手机…

4.3-内置后置PostProcess处理器深度讲解

在reader里面注册了很多Bean定义 reader会调取register()来注册配置类 调用上句&#xff0c;就会把配置类注册到BeanDefinitionMap中去 配置类有了、解析配置类的处理器有了 然后&#xff0c; 在第三步refresh() 进行IOC容器刷新中的invokeBeanPostProcessors(beanFactory…

Java密码学之加解密

前篇&#xff1a;Java密码学之数字签名_东皋长歌的博客-CSDN博客 日常开发中用的比较多的功能点&#xff0c;加解密数据&#xff0c;用Java实现也是很快很实用。 下面记录一下加解密数据的过程。 1&#xff0c;创建密钥对生成器 KeyPairGenerator keyPairGen KeyPairGener…

我的新书《Java编程动手学》

关于这本书 很高兴&#xff0c;我又一本书籍《Java编程动手学》上市了。记得早在2017年&#xff0c;在我跟人邮出版社的傅道坤编辑合作完《Tomcat内核设计剖析》这本书后&#xff0c;傅编就问我考不考虑写一本面向Java初学者的图书&#xff0c;当时我谢绝了傅编的邀请。一来是…

21.Xaml Expander控件--->可折叠的带标题的内容控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

day55:C++ day5,运算符重载剩余部分、静态成员、继承

#include <iostream> #include <cstring> #define pi 3.14 using namespace std;class Shape { protected:double round;double area; public://无参构造Shape():round(40),area(100){cout<<"Shape::无参构造函数&#xff0c;默认周长为40&#xff0c;面…

渐变线条拖尾特效-原理及pygame实现

文章目录 效果预览视频教程代码内容介绍总结更多宝藏 效果预览 &#x1f60e;&#x1f973;&#x1f60e;&#x1f920;&#x1f916;&#x1f648;&#x1f4ad;&#x1f373;&#x1f371; 视频教程 https://www.bilibili.com/video/BV1vu411A7zy/ 代码 import pygame# 初…

基于HOG特征提取和GRNN神经网络的人脸表情识别算法matlab仿真,测试使用JAFFE表情数据库

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 1.HOG特征提取 2.GRNN神经网络 3.JAFFE表情数据库 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .....................................…

解决MySQL数据库拒绝远程计算机连接问题

错误信息&#xff1a;Host is not allowed to connect to this mysql server 以前MySQL数据库部署在云服务器上&#xff0c;程序服务端也部署在云服务器上&#xff0c;连接服务器从没出现过问题。最近有一次需要做一个完全局域网的环境部署&#xff0c;我把数据库和程序服务端…

python爬虫教程:用scrapy实现模拟登录

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 背景&#xff1a; 初来乍到的pythoner&#xff0c;刚开始的时候觉得所有的网站无非就是分析HTML、json数据&#xff0c;但是忽略了很多的一个问题&#xf…

Python第一次作业练习

题目分析&#xff1a; """ 参考学校的相关规定。 对于四分制&#xff0c;百分制中的90分及以上可视为绩点中的4分&#xff0c;80 分及以上为3分&#xff0c;70 分以上为2分&#xff0c;60 分以上为1分; 五分制中的5分为四分制中的4分&#xff0c;4分为3分&#…

Win10声音无法找到输出设备怎么办

近期有小伙伴反映在使Win10的过程中无法找到声音输出设备&#xff0c;从而导致电脑的声音无法正常的播放&#xff0c;这是怎么回事呢&#xff0c;遇到这种情况应该怎么解决呢&#xff0c;下面小编就给大家详细介绍一下Win10声音无法找到输出设备的解决方法&#xff0c;大家可以…