二叉搜索树的oj

news2025/1/19 11:07:07

目录

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

二、二叉树的层序遍历

三、二叉树的层序遍历Ⅱ

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

①递归求解

②回溯求解

五、二叉搜索树和双向链表

六、根据一棵树的前序遍历与中序遍历构造二叉树

七、从中序与后序遍历序列构造二叉树 

八、二叉树的任一遍历,非递归迭代实现



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



题目概述:

给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。

空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

 

这道题目就是简单的前序遍历,我们先不考虑省略所有不必要的空括号对。如果不省略,就是每次遍历根,然后添加括号和左子树,然后是右子树,这样看来我们会得到包含空括号对的解。然后对需要去除的情况进行分析发现:

1、如果没有左子树并且没有右子树就不需要添加括号

2、如果没有左子树但是有右子树,则空左子树也需要添加括号

class Solution {
public:
    string tree2str(TreeNode* root) 
    {
        //根 左子树 右子树
        //一般来说就是根 左子树 右子树
        if(root==nullptr)
            return string();
        string ans;
        ans+=to_string(root->val);
//不必要的空括号,如果右为空,就没必要加了,如果左为空,但是右不为空,就要加
//
        if(root->left)
        {
            ans+='(';
            ans+= tree2str(root->left);
            ans+=')';
        }
        else if(root->right)//左为空,但是右不为空
        {
            ans+="()";
        }
        if(root->right)
        {
            ans+='(';
            ans+=tree2str(root->right);
            ans+=')';
        }
        return ans;

    }
};

二、二叉树的层序遍历


 

题目概述:

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

 二叉树的层序遍历,借助队列将每一层的节点存储到队列中。比如第一层是3.当队列不空的时候,取队列对头的节点,值存入数组中,如果队头节点有左右孩子,将它的左右孩子节点存入队列。问题是要我们返回一个二维数组,每一个数组都代表一行的节点,怎么监测是在这一行呢?我们假设一个levelSize来记录每一行节点的个数,比如第一个行只有一个根节点,levelSize==1,当访问过根节点levelSize就--,当levelSize减为0的时候,此时队列的大小就是下一行节点的个数!

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        if(root==nullptr)
            return  vector<vector<int>>();
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        //记录每一层的个数
        int levelSize=1;
        q.push(root);
        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;
    }
};

三、二叉树的层序遍历Ⅱ


题目概述:

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

 理解了上一道题目,这道题目就是小意思了,我们按照正序的方式存储下来后,再翻转一下就可以了。

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) 
    {
        if(root==nullptr)
         return vector<vector<int>>();
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        q.push(root);
        int levelSize=1;
        while(!q.empty())
        {
            vector<int> levelv;
            while(levelSize--)
            {
                TreeNode* front=q.front();
                q.pop();
                levelv.push_back(front->val);
                if(front->left)
                    q.push(front->left);
                if(front->right)
                    q.push(front->right);
                
            }
            vv.push_back(levelv);
            levelSize=q.size();
        }
        reverse(vv.begin(),vv.end());
        return vv;

    }
};

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


题目概述:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

①递归求解

通过分析我们可以列举出所有的情况。

 

很轻易就写出递归代码:

class Solution {
public:
    bool judgeTree(TreeNode* root,TreeNode* x)
    {
        if(root==nullptr)
            return false;
        return root==x|| judgeTree(root->left,x)
                ||judgeTree(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=judgeTree(root->left,p);
        bool pInright=!pInleft;
        bool qInleft=judgeTree(root->left,q);
        bool qInright=!qInleft;

        //是否在左边
        if(pInleft&&qInright||pInright&&qInleft)
        {
            return root;
        }
        if(pInleft&&qInleft)
            return lowestCommonAncestor(root->left,p,q);
        else
            return lowestCommonAncestor(root->right,p,q);
    }
};

 这种方式,比较好理解,但是对于极端情况:

对于这样的情况,它的时间复杂度就退化为O(N^2)。

②回溯求解

对于p和q节点,我们是否能求出他们的路径呢?

比如:

 

p节点也就是7的路径是:3 5 2 7

q节点也就是0的路径是:3 1 0

他们的路径我们用栈来存储,得到路径后就转化为链表相交问题。

求解路径的过程就是回溯的过程。比如我们要找7,先存入3,然后到3的左子树,存入5,5不是我们要找的节点,存入6,然后发现6的左右孩子都为空,那么说明6的左右子树找不到,返回false,同时将6节点 pop出栈。然后去5的右子树查找。(如果5的右子树也找不到,5也要被pop出栈)

代码:

class Solution {
public:
    bool GetPath(TreeNode* root,TreeNode* x,stack<TreeNode*>& path)
    {
        if(root==nullptr)
            return false;
        //先入栈
        path.push(root);
        if(root==x)
            return true;//如果是就返回true
        //不是,去左子树记录路径
        if(GetPath(root->left,x,path))//在左子树是否能找到
            return true;
        if(GetPath(root->right,x,path))
            return true;//去右子树查找
        //都找不到,就pop掉
        path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        
        //记录路径
        stack<TreeNode*> pPath;//p的路径
        stack<TreeNode*> qPath;//q的路径
        GetPath(root,p,pPath);
        GetPath(root,q,qPath);//q的路径

        while(qPath.size()!=pPath.size())
        {
            if(qPath.size()>pPath.size())
                qPath.pop();
            else
                pPath.pop();
        }
        //链表相交
        while(qPath.top()!=pPath.top())
        {
            qPath.pop();
            pPath.pop();
        }
        return pPath.top();

    }
};

五、二叉搜索树和双向链表


题目概述:

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

 比较暴力的一种解法,就是创建一个vector,将每个节点拆下来,然后一个一个去改变指向,这样一来这题目就变成了一个简单题。不过如果加限制条件空间复杂度为O(1)呢?

我们对于每个节点,我们无法预知之后的节点,但是我们可以记录改变之前遍历过的节点!设一个prev前驱指针,cur当前指针。每次cur遍历,prev的右指针指向cur,cur的左指针指向prev。整体的逻辑就是二叉树的中序遍历。不过需要注意的是我们传的是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* pRootOfTree) 
	{
		if(pRootOfTree==nullptr)
			return nullptr;
		//传引用
		TreeNode* prev=nullptr;
		//中序遍历逻辑
		InOrderConvert(pRootOfTree,prev);
		//找头
		TreeNode* head=pRootOfTree;
		while(head->left)
		{
			head=head->left;
		}
		return head;
        
    }
};

六、根据一棵树的前序遍历与中序遍历构造二叉树

 六、根据一棵树的前序遍历与中序遍历构造二叉树

题目概述:

 前序遍历是根、左子树和右子树。中序遍历是左子树、根和右子树。这道题目采用分治的思想,前序遍历可以确定根,根据根去中序遍历里面查找,找到根以后,根的左区间为它的左子树,根的右区间为它的右子树。这样我们采用递归的思想,就可以进一步转换为子问题。前序遍历仅供我们查找根,中序遍历用于确定区间。

代码:

class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder,int inbegin,int inend,int& prei)
    {
        if(inbegin>inend)
            return nullptr;
        int rooti=inbegin;
        while(rooti<=inend)
        {
            if(preorder[prei]==inorder[rooti])
                break;
            ++rooti;
        }
        TreeNode* root=new TreeNode(preorder[prei++]);
        root->left=_buildTree(preorder,inorder,inbegin,rooti-1,prei);
        root->right=_buildTree(preorder,inorder,rooti+1,inend,prei);
        return root;

    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        int pi=0;
        return _buildTree(preorder,inorder,0,inorder.size()-1,pi);

    }
};

七、从中序与后序遍历序列构造二叉树 



从中序与后序遍历序列构造二叉树

题目概述:

 后序遍历:左子树、右子树、根。中序遍历:左子树、根、右子树

后序遍历倒着查找根,因为我们倒着查找根,并且后序遍历的次序是左子树、右子树、根,所以我们重建二叉树的次序应该是根、右子树、左子树,和上题不同的是,先构建右子树再构建左子树。

代码:
 

class Solution {
public:
    TreeNode* _buildTree(vector<int>& inorder,vector<int>& postorder,int inbegin,int inend,int& rooti)
    {
        if(inbegin>inend)
            return nullptr;
        int exi=inbegin;
        while(exi<=inend)
        {
            if(postorder[rooti]==inorder[exi])
                break;
            ++exi;
        }
        TreeNode* root=new TreeNode(postorder[rooti--]);
        root->right=_buildTree(inorder,postorder,exi+1,inend,rooti);
        root->left=_buildTree(inorder,postorder,inbegin,exi-1,rooti);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) 
    {
        //中序遍历:左子树、根、右子树
        //后序遍历:左子树 右子树 根
        int pi=postorder.size()-1;
        return _buildTree(inorder,postorder,0,inorder.size()-1,pi);

    }
};

八、二叉树的任一遍历,非递归迭代实现

前序遍历:

 这个图比较不容易分析,我们换一张图:

 向左深度遍历,用栈存储左子树节点,以方便我们取到右子树节点。因为是前序遍历,所以每遍历到一个节点,都相当于它所在树的根,都要存到数组中。这样我们就很容易实现它的非递归实现。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        if(root==nullptr)
        {
            return vector<int>();
        }
        vector<int> v;
        stack<TreeNode*> st;
        //3   9   7
        //因为是根、左子树、右子树
        //所以要先把根存入vector
        TreeNode* cur=root;
        while(cur||!st.empty())
        {
            while(cur)
            {
                st.push(cur);
                v.push_back(cur->val);
                cur=cur->left;
            }
            //取st中的节点
            TreeNode* top=st.top();
            st.pop();
            cur=top->right;
        }
        return v;

    }
};

中序遍历

中序遍历也很简单,只是先访问左子树,再访问根。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) 
    {
        if(root==nullptr)
            return vector<int>();
        vector<int> v;
        stack<TreeNode*> st;
        TreeNode* cur=root;
        while(cur || !st.empty())
        {
            while(cur)
            {
                st.push(cur);
                cur=cur->left;
            }
            //3  5  6
            TreeNode* top=st.top();
            st.pop();
            v.push_back(top->val);
            cur=top->right;
        }
        return v;

    }
};

后序遍历

后序遍历的顺序是:左子树、右子树、根。我们如果还采用上面的办法,需要解决一个问题,那就是根的访问和从栈中pop出节点的时机,这是十分重要的。

比如上图,我们向左子树dfs遍历,栈中存下3、5、6.这时我们取栈顶元素6,取完后可以pop出栈吗?可以!因为6的右子树为空。 我们再取5,5可以访问存入数组吗?不可以,因为5的右子树还没访问,所以我们需要访问完5的右子树才能pop出5.也就是说这时将根存入数组和pop出栈顶元素的条件是:该节点的右子树为空,或者上次访问的节点是该节点的右孩子!我们可以设prev为上次访问节点。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) 
    {
        if(root==nullptr)
            return vector<int>();
        vector<int> v;
        stack<TreeNode*> st;
        TreeNode* cur=root;
        TreeNode* prev=nullptr;
        //左子树 右子树 根
        while(cur || !st.empty())
        {
            while(cur)
            {
                st.push(cur);
                cur=cur->left;
            }
            //3  5  6
            // 访问右子树的条件是右子树为空
            TreeNode* top=st.top();
            //暂时不能pop
            //pop的前提是访问过右子树才能pop
            if(top->right==nullptr||prev==top->right)
            {
                st.pop();
                v.push_back(top->val);
                prev=top;
            }
            else
            {
                cur=top->right;
            }
        }
        return v;

    }
};

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

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

相关文章

大厂面试-算法优化:冒泡排序你会优化吗?

关注公众号&#xff1a;”奇叔码技术“ 回复&#xff1a;“java面试题大全”或者“java面试题” 即可领取资料 原文&#xff1a;冒泡排序及优化代码 https://blog.csdn.net/weixin_43989347/article/details/122025689原文&#xff1a;十大经典排序算法 https://frxcat.fun/pa…

矩阵相似题型总结

矩阵相似题型总结 &#xff08;一&#xff09;题型一&#xff1a;判断含重特征值的方阵A能否相似对角化&#xff08;即能否相似于对角阵&#xff09; 所有特征值均不同的矩阵必可相似对角化&#xff0c;含重特征值的矩阵呢&#xff1f;可如下做 &#xff08;1&#xff09;求A的…

C++linux高并发服务器项目实践 day4

Clinux高并发服务器项目实践 day4模拟实现ls -l指令文件属性操作函数access函数chmod 与chowntruncate函数目录操作函数mkdir和rmdirrenamechdir和getcwd目录遍历函数dup、dup2函数dupdup2fcntl函数模拟实现ls -l指令 #include<sys/types.h> #include<sys/stat.h>…

为何巴菲特和马斯克站在了一起?

股神巴菲特虽然非常传奇&#xff0c;但是马斯克对其并不感冒。马斯克曾经在一档电视节目中表示&#xff0c;实业才是王道&#xff0c;埋怨金融业抢走太多人才和精英&#xff0c;暗指巴菲特为年轻人做了错误示范。当然&#xff0c;巴菲特的投资非常厉害&#xff0c;但也有失手的…

2-修改example适用于不同开发板

1.问题 手上只有基于nRF52811芯片的BMD360开发板,与pca10056e开发板同一个芯片.所以pca10056e的example都可以适用于BMD360开发板,只需要修改开发板相同的输入输出硬件管脚即可.因为BMD360开发板与pca10056e开发板的输入输出管脚不同. 而BMD360开发板输入输出管脚于PCA10040相同…

【数据结构】七大排序算法详解Java

目录 1.排序算法分类 1.直接选择排序 代码展示&#xff1a; 2.直接插入排序 核心思路&#xff1a; 代码展示&#xff1a; ​编辑 3.希尔排序 思路分析&#xff1a; 代码展示&#xff1a; 4.归并排序 代码展示&#xff1a; 5.快速排序(挖坑法) 思路分析&#xff1a; …

OJ系统刷题 第十篇

13444 - 求出e的值 时间限制 : 1 秒 内存限制 : 128 MB 利用公式e11/!1​1/2!​1/3!​...1/n!​&#xff0c;求e的值&#xff0c;要求保留小数点后10位。 输入 输入只有一行&#xff0c;该行包含一个整数n&#xff08;2≤n≤15&#xff09;&#xff0c;表示计算e时累加到1/…

计算机操作系统第四版第六章输入输出系统—课后题答案

1.试说明I/O系统的基本功能。 隐藏物理设备的细节、与设备的无关性、提高处理机和I/O设备的利用率、对I/O设备进行控制、确保对设备的正确共享、错误处理 2.简要说明I/O软件的4个层次的基本功能。 用户层I/O软件&#xff1a;实现与用户交互的接口&#xff0c;用户可直接调用该层…

4.8、socket介绍

4.8、socket1. socket介绍1. socket介绍 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位…

阿里云centos7搭建ChatGPT网站

需要的环境 有一台外网的服务器 拥有一个OpenAI API Key Centos7&#xff08;其他服务器也行&#xff09; nodejs 前端github上 大神写的 https://github.com/ddiu8081/chatgpt-demo/ 一.安装node.js centos7 安装node.js 二.安装pnpm npm i -g pnpm三.下载web前端项目从…

【2023最新】超详细图文保姆级教程:App开发新手入门(3)

上文回顾&#xff0c;我们已经完成了一个应用项目创建、导入、代码更新、代码同步和代码提交&#xff0c;本章继续我们的新手开发之旅&#xff0c;讲述一下如何将开发完成的应用进行编译&#xff0c;生成可供他人安装、可上架的应用安装包。 6 应用打包 应用打包&#xff0c;…

27个必备的Python技巧,你一定要知道!

目录 01. 为什么使用缩进来分组语句&#xff1f; Guido van Rossum 认为使用缩进进行分组非常优雅&#xff0c;并且大大提高了普通 Python 程序的清晰度。大多数人在一段时间后就学会并喜欢上这个功能。 由于没有开始/结束括号&#xff0c;因此解析器感知的分组与人类读者之间…

免费英文在线翻译-英文自动翻译

免费的自动翻译器 作为一款免费的自动翻译器&#xff0c;我们的产品可以为全球用户提供高质量、高效率的翻译服务&#xff0c;帮助他们更好地沟通和交流。 现在&#xff0c;随着数字化的进一步发展&#xff0c;人们之间的跨文化交流越来越频繁。然而&#xff0c;语言偏差和文…

22-JavaScript

目录 1.什么是JavaScript&#xff1f; 1.1.JS、HTML、CSS关系 1.2.JS是如何运行的&#xff1f; 2.JS前置知识 2.1.第一个JS程序 PS&#xff1a;JS书写位置 2.2.JS书写格式 2.2.1.行内格式 ​2.2.2.内嵌格式 ​2.2.3.外部格式 2.3.注释&#xff08;script标签中&…

【软件测试】测试用例

目录 &#x1f337;1. 测试用例的基本要素 &#x1f337;2. 测试用例的设计方法 &#x1f333;2.1 基于需求进行测试用例的设计 ⭐️&#xff08;1&#xff09;功能需求测试分析 ⭐️&#xff08;2&#xff09;非功能需求测试分析 &#x1f333;2.2 具体的设计方法 &#…

【Python搞笑游戏】因蔡徐坤打篮球动作超火,被某程序员写成了一款游戏,画面美到不敢看,成功学到了精髓~(附源码免费)

导语 之前网络最火的梗&#xff0c;非“C徐坤打篮球”莫属。个人感觉&#xff0c;只有多年前的“春哥纯爷们”堪与匹敌&#xff01; 虽然说C徐坤打篮球是一个老梗了&#xff0c;但是确实非常搞笑&#xff0c;今天就跟着小编一起来回忆一下吧&#xff01; “我是练习两年半的…

qt - 隐式共享与d-pointer技术

文章目录前言1. 隐式共享2. d-pointer在隐式共享中的应用3. 二进制代码兼容4. d-pointer模式的实现5. QObject中的d-pointer前言 一般情况下&#xff0c;一个类的多个对象所占用的内存是相互独立的。如果其中某些对象数据成员的取值完全相同&#xff0c;我们可以令它们共享一块…

ESP32学习二-更新Python版本(Ubuntu)

一、简介 在一些场景里边&#xff0c;因为Python的版本过低&#xff0c;导致一些环境无法安装。这里来介绍以下&#xff0c;如何升级自己已安装的Python版本。例如如下情况&#xff1a; 二、实操 1.查看本地版本 python --version 2.添加源 sudo add-apt-repository ppa:jona…

FPGA时序知识点(基本方法总结就两点:1.降低时钟频率2.减小组合逻辑延迟(针对Setup Slack公式来的)

1.我们说的所有时序分析都是建立在同步电路的基础上的&#xff0c;异步电路不能做时序分析&#xff08;或者说只能做伪路径约束&#xff08;在设伪路径之前单bit就打拍&#xff0c;多bit就异步fifo拉到目的时钟域来&#xff09;&#xff09;。——FPGA 设计中寄存器全部使用一个…

逐一解释一下四个 “内存屏障” 是什么

什么是内存屏障&#xff1f;硬件层⾯&#xff0c;内存屏障分两种&#xff1a;读屏障&#xff08;Load Barrier&#xff09;和写屏障&#xff08;Store Barrier&#xff09;。内存屏障有两个作⽤&#xff1a; 阻⽌屏障两侧的指令重排序&#xff1b;强制把写缓冲区/⾼速缓存中的…