【二叉树经典题目】

news2024/11/23 3:01:12

根据二叉树创建字符串

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

  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.coloradmin.cn/o/1153449.html

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

相关文章

杰林码纠错算法库(lib、dll)以及AWGN信道BPSK信号下的仿真程序(C/C++)

2023年10月30日此次是我最后一次在国内发布纠错算法的测试程序&#xff0c;这个算法2018年左右就出来了第一个版本&#xff0c;部分网络上也能下载到测试程序&#xff0c;尽管以前的版本效率跟不上&#xff0c;而且码率比较固定只能支持0.63。通过几年的努力&#xff0c;我这次…

分享一下怎么做一个同城配送小程序

如何制作一个同城配送小程序&#xff1a;功能特点、使用指南及未来展望 一、引言 随着互联网的快速发展&#xff0c;人们对于生活服务的需求越来越高。同城配送作为连接消费者与商家的桥梁&#xff0c;越来越受到人们的关注。本文将详细介绍如何制作一个同城配送小程序&#…

oracel处理XML时,报ORA-31011、ORA-19202。

原字段为clob&#xff0c; 查询 SELECT XMLTYPE(字段) FROM TABLE_A报错如下&#xff1a; ORA-31011: XML 语法分析失败 ORA-19202: XML 处理 LPX-00217: invalid character 12 (U000C) Error at line 1559时出错 ORA-06512: 在 "SYS.XMLTYPE", line 272 ORA-0651…

3ds Max2022安装教程(最新最详细)

目录 一.简介 二.安装步骤 网盘资源见文末 一.简介 3DS Max是由Autodesk公司开发的一款专业三维建模、动画和渲染软件&#xff0c;广泛应用于影视、游戏、建筑和工业设计等领域。 3DS Max的主要特点和功能包括&#xff1a; 三维建模&#xff1a;3DS Max提供了各种强大的建…

【数据结构】数组和字符串(十一):字符串的定义与存储(顺序存储、链式存储及其C语言实现)

文章目录 4.3 字符串4.3.1 字符串的定义与存储1. 顺序存储2. 链式存储3. C语言实现顺序存储4. C语言实现链式存储代码优化 4.3 字符串 字符串(String)是由零个或多个字符(char)顺序排列组成的有限序列&#xff0c;简称为串。例如 “good morning”就是由12个字符构成的一个字符…

优思学院|制作SPC控制图一定要用Minitab吗?

如果是使用SPC控制图作为一种控制过程变异的工具&#xff0c;无需使用Minitab&#xff0c;用Excel已经相当足够。但无论你使用哪种工具&#xff0c;你都应该要先明白SPC或者控制图工具的目的是什么&#xff0c;以及如何选择合适的控制图&#xff0c;以及如何去解读它等等。 要…

从云到AI到大模型,阿里云始终和谁站在了一起?

引言&#xff1a;云的创新没有减缓 而且在加速深入百行百业 【科技明说 &#xff5c; 热点关注】 【全球云观察 | 每日看点】作为中国最大的云计算厂商&#xff0c;阿里云的产品矩阵覆盖越来越全面&#xff0c;越来越细致&#xff0c;越来越到位。 ​ 在2023杭州云栖大会现场…

如何使用ArcGIS Pro制作带基底三维地图

使用ArcGIS制作三维地形图相信大家都已经比较熟悉了&#xff0c;现在Esri主推的桌面GIS产品是ArcGIS Pro&#xff0c;这里为大家介绍一下ArcGIS Pro制作三维地图的方法&#xff0c;希望能对你有所帮助。 01数据来源 本教程所使用的数据是从水经微图中下载的DEM数据&#xff…

Java体系性能测试进阶必须了解的知识点——GC日志分析

GC定义 GC&#xff08;Garbage Collection&#xff09;是垃圾收集的意思,内存处理是程序员编码容易产生问题的地方&#xff0c;忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃&#xff0c;Java提供的GC功能可以自动监测对象是否需要进行垃圾回收。所以&#xff0c;J…

有质更有智,最低10.88万的哪吒X有多超值?

近期,哪吒汽车可谓是存在感极高,先是官宣娜扎为品牌代言人,再是哪吒X正式上市,更有哪吒GT运动版和官改套件上线等一系列大动作,频频霸榜车圈热搜。近日,首台哪吒X于哪吒汽车嘉兴直营中心交付的消息也在朋友圈刷屏,从正式上市到首台交付,仅仅相隔两天时间,真正实现“上市即交付”…

【pdf密码】PDF没有密码,为什么不能编辑?

打开PDF文件的时候&#xff0c;没有提示带有密码&#xff0c;但是打开文件之后发现没有办法编辑PDF文件&#xff0c;这个是因为PDF文件设置了限制编辑&#xff0c;我们需要将限制取消才能够编辑文件。 那么&#xff0c;我们应该如何取消密码&#xff0c;编辑文件呢&#xff1f…

Linux系统封装ISO镜像(自动安装)

一、准备一个系统 centos7或者centos8都可以;最小化或者桌面版的都可以,自行选择 二、安装自定义镜像工具 yum -y install createrepo mkisofs openssl rsync syslinux三、挂载镜像 创建挂载点 mkdir /mnt/cdrommount /dev/sr0 /mnt/cdrom四、同步 /mnt/cdrom/ 下的文件到 …

分享一下微信小程序里怎么开店

如何在微信小程序中成功开店&#xff1a;从选品到运营的全方位指南 一、引言 随着微信小程序的日益普及&#xff0c;越来越多的人开始尝试在微信小程序中开设自己的店铺。微信小程序具有便捷、易用、即用即走等特点&#xff0c;使得开店门槛大大降低。本文将详细介绍如何在微…

vue-query的使用

vue-query&#xff0c;类似于vuex/pinia&#xff0c;以缓存为目的&#xff0c;但侧重的是对网络请求的缓存。 这是我预想的使用场景&#xff1a;假设在各个页面都需要发起相同的请求&#xff0c;去获取数据&#xff0c;而这种数据在一定时间内不会发生变化&#xff0c;那么这种…

优思学院:从西格玛计算出Cpk与不良率PPM

在品质控制的领域中&#xff0c;Cpk&#xff08;制程能力指数&#xff09;和PPM&#xff08;每百万个中不良品的个数&#xff09;是两个关键的指标&#xff0c;用来评估一个制程的稳定性和一致性。然而&#xff0c;这两者的计算并不简单&#xff0c;特别是当规格中心值与物品量…

openfeign和全局异常

我们在seata中使用openfeign调用服务的时候经常会出现一些问题 (1)在使用openfeign的时候使用到了全局异常,本来feign调用失败会触发降级异常,但是如果加上 全局异常处理器的话可能不会触发,导致降级失败 (2)服务a调用服务b的接口b(),b接口调用出错了,服务b本来应该返回异常,但…

最新ai系统ChatGPT商业运营版网站源码+支持GPT4.0/支持AI绘画+已支持OpenAI GPT全模型+国内AI全模型+绘画池系统

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

GPU渲染中各个步骤的作用

MainLightShadow(主光照阴影&#xff09;&#xff1a; 通常在渲染管线中的最开始阶段进行绘制&#xff0c;这是因为主要光源的阴影对于场景中的物体渲染和视觉效果非常重要。 下面是一些原因&#xff0c;解释为什么主光照阴影往往是在最开始绘制的&#xff1a; 1、视觉效果的…

关闭谷歌浏览器的自动更新 详细教程

1.前往资源库找到Google文件夹 2.进入找到GoogleSoftwareUpdate.bundle 并且删除 弹出删除框 需要我们使用指纹或者锁屏密码 就可以删除了 3.打开谷歌浏览器查看是否已经不再自动更新了 发现上面提示更新失败即可 将不会再次更新 window/其他电脑关闭自动更新教程参考&#x…

MFC实现堆栈窗口:多个子界面可任意切换

1、效果 在Qt中可使用QStackedWidget控件直接拖动布置即可实现&#xff0c;但在MFC中并未提供类似的控件&#xff0c;因此需要自己简单实现。 2、实现原理 实现原理比较简单&#xff0c;父级对话框在显示的区域部分&#xff0c;通过切换子对话框即可实现。子对话框去掉边框后…