数据结构:二叉树—面试题(二)
1、二叉树的最近公共祖先
习题链接https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/
描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

解题思路
对于这道题我们有两种思路来解决这个问题
思路一:
首先,我们要去找到p,q这两个结点,我们从根节点开始找,如果根结点属于p或者q,我们就直接返回,此时的根结点就是p,q的最近公共祖先。
但我们的根节点不是p,q我们就去根的左子树和右子树去找,如果根的左右两个结点还不是,我们就继续向下找,因此我们需要利用递归实现。
到最后了我们还是没有找到,我们就返回null,如果找到了就返回p或者q的结点,当我们得到了p和q这俩个结点后,还要得到最近的公共祖先,如果这两个返回值不为空,就返回root(此时递归回来两个结点的父节点),如果一个为空一个不为空就返回不为空的结点。
思路二:
首先,我们还是从根结点开始找,如果根结点属于p或者q,我们就直接返回,此时的根结点就是p,q的最近公共祖先。
但如果我们的根节点不是,我们就创建两个栈,将从根节点到p的结点放到stack1这个栈中,将从根节点到q的结点放到stack2中。
首先我们从根节点开始放,如果不是就走左子树,走完左子树再走右子树,走一个结点就入栈一个,如果在递归往下走的过程中,如果此时是左边为空,就返回false,然后就向右边走,如果右边也为空就弹出这个结点,并返回false,如果最后, 如果左右两边找到了就返回true.
最后我们得到的两个栈,存放的就是从根节点到p,q的所有结点,此时我们计算栈的长度,求出差值,如果一个栈长,就让这个栈弹出差值个元素,让这两个栈是相同的长度,然后让这两个栈一起走,如果在走的过程中他们相遇了此时相遇的值就是我们的p,q最近公共祖先。
完整代码
思路一:
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null){return null;}if(root == p || root == q){return root;}TreeNode leftNode = lowestCommonAncestor(root.left,p,q);TreeNode rightNode = lowestCommonAncestor(root.right,p,q);if(leftNode != null && rightNode != null){return root;}else if(leftNode != null){return leftNode;}else if(rightNode != null){return rightNode;}return null;}
}
思路二:
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null){return null;}if(root == p || root == q){return root;}Stack<TreeNode> stack1 = new Stack<>();Stack<TreeNode> stack2 = new Stack<>();TreeNodeStack(root,p,stack1);TreeNodeStack(root,q,stack2);int size = stack1.size() - stack2.size();if(size < 0){size = Math.abs(size);while(size != 0){stack2.pop();size--;}}else{while(size != 0){stack1.pop();size--;}}while(!stack1.isEmpty() && !stack2.isEmpty()){if(stack1.peek() != stack2.peek()){stack1.pop();stack2.pop();}else{return stack1.pop();}}return null;}public boolean TreeNodeStack(TreeNode root,TreeNode node,Stack<TreeNode> stack){if(root == null){return false;}stack.add(root);if(root == node){return true;}boolean flg = TreeNodeStack(root.left,node,stack);if(flg){return true;}flg = TreeNodeStack(root.right,node,stack);if(flg){return true;}stack.pop();return false;}
}
2、从前序与中序遍历序列构建二叉树
习题链接https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/
描述:
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

解题思路
他要我们根据前序和中序创建二叉树,首先,我们要创建一个变量preorderindex,作为一个引用指向前序遍历,并在设立两个变量inbegin和inend代表中序遍历的第一个和最后一个位置。
我们知道,我们前序遍历的第一个结点就是我们的根节点,此时我们在去中序遍历中找这个节点,此时在中序遍历中这个结点的左边就是我们的左子树的所有结点,右边是我们右子树的所有结点。
而我们左子树的第一个结点就是我们前序遍历的第二个结点,而我们左子树要在中序遍历找的范围就是我们的最左边inbegin和我们的根节点的位置减一,而我们右子树要在中序遍历找的范围就是我们的最右inend和我们的根节点的位置加一,每创建一个结点就让preorderindex++。
最后一点我们什么时候什么时候停止创建,就是我们中序遍历中的inbegin和inend相遇了或者inbegin超过inend。
完整代码
class Solution {public int preorderindex = 0;public TreeNode buildTree(int[] preorder, int[] inorder) {return buildTreeChild(preorder,inorder,0,inorder.length-1);}public TreeNode buildTreeChild(int[] preorder, int[] inorder,int inbegin,int inend) {if(inbegin > inend){return null;}TreeNode root = new TreeNode(preorder[preorderindex]);int rootindex = findindex(inorder,inbegin,inend,preorder[preorderindex]);preorderindex++;root.left = buildTreeChild(preorder,inorder,inbegin,rootindex-1);root.right = buildTreeChild(preorder,inorder,rootindex+1,inend);return root;}public int findindex(int[] inorder,int inbegin,int inend,int key){for(int i = inbegin ;i<=inend;i++){if(inorder[i] == key){return i;}}return -1;}
}
3、从中序与后序遍历序列构建二叉树
习题链接https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/
https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/
描述:
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

解题思路
这道题和我们上面的方式是一样的,只是我们找根节点是从后序遍历的最后一个结点开始找,并且我们要先建立右子树在建立左子树,因为我们后序遍历的方式是左右根,我们根是跟着右树相连的。
完整代码
class Solution {public int postorderindex =0;public TreeNode buildTree(int[] inorder, int[] postorder) {postorderindex = postorder.length-1;return buildTreeChild(inorder,postorder,0,inorder.length-1);}public TreeNode buildTreeChild(int[] inorder, int[] postorder,int inbegin ,int inend) {if(inbegin > inend){return null;}TreeNode root = new TreeNode(postorder[postorderindex]);int rootindex = findindex(inorder,inbegin,inend,postorder[postorderindex]);postorderindex--;root.right = buildTreeChild(inorder,postorder,rootindex+1,inend);root.left = buildTreeChild(inorder,postorder,inbegin,rootindex-1);return root;}public int findindex(int[] inorder,int inbegin,int inend,int key){for(int i = inbegin;i<= inend;i++){if(inorder[i] == key){return i;}}return -1;}
}
4、根据二叉树创建字符串
习题链接https://leetcode.cn/problems/construct-string-from-binary-tree/description/
https://leetcode.cn/problems/construct-string-from-binary-tree/description/
描述:
给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。
空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。


解题思路
根据这道题的示例,其实我们能发现他的创建规则。
首先我们先将根节点传入字符串中,然后如果左子树不为空就不断向下走左子树,每遇到一个左节点不为空的结点就先在字符串中添加一个左括号“(”,然后再利用递归添加这个结点的值,并不断的往下走如果我们的左节点为空了,但是右节点不为空就添加一个“()”,如果左右都为空就直接返回,并利用递归添加一个“)”。
最后我们的左子树的结点已经全部添加到字符串上了,我们再去添加右子树,如果右子树不为空,我们还是去添加一个“(”,然后再去添加右子树的结点,如果最后最后右子树为空了,并且能走到右子树也代表左子树为空,此时就直接返回并利用递归添加一个“)”。
完整代码
class Solution {public String tree2str(TreeNode root) {StringBuilder stringBuilder = new StringBuilder();tree2strChild(root,stringBuilder);return stringBuilder.toString();}public void tree2strChild(TreeNode root,StringBuilder stringBuilder) {if(root == null){return ;}stringBuilder.append(root.val);if(root.left != null){stringBuilder.append("(");tree2strChild(root.left,stringBuilder);stringBuilder.append(")");}else{if(root.right != null){stringBuilder.append("()");}else{return ;}}if(root.right != null){stringBuilder.append("(");tree2strChild(root.right,stringBuilder);stringBuilder.append(")");}else{return;}}
}
5、二叉树的前序遍历
习题链接https://leetcode.cn/problems/binary-tree-preorder-traversal/description/
https://leetcode.cn/problems/binary-tree-preorder-traversal/description/
描述:
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。


解题思路
根据题意就是要得到我们二叉树的前序遍历,首先我们创建一个链表来接收前序遍历的值,如果二叉树为空直接返回,不为空就创建一个栈,和两个节点,cur指向root,tap指向空。
然后我们要利用循环得到前序遍历的结果,我们知道前序遍历的顺序是:根左右,因此我们要先去左树,我们从根结点开始入栈,每入一个节点就在链表中添加一个结点值,并往下向左结点走,如果此时我们的左节点为空了,我们就弹出此时的栈顶元素,即此时左节点的根节点,给tap,在让cur指向tap的右结点,如果此时的右结点不为空,就放入到栈和链表中,并继续走左节点,为空就重复上面的操作,但如果此时的右节点为空,就代表新的cur为空,但是此时栈不为空,我们还是进入循环,而这样的操作就返回到了我们的上一层。于是在不断循环下就得到了我们的前序遍历
完整代码
class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> ans = new LinkedList<>();if(root == null){return ans;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode tap = null;while(cur != null || !stack.isEmpty()){while(cur != null){stack.push(cur);ans.add(cur.val);cur = cur.left;}tap = stack.pop();cur = tap.right;}return ans;}
}
6、二叉树的中序遍历
习题链接https://leetcode.cn/problems/binary-tree-inorder-traversal/description/
https://leetcode.cn/problems/binary-tree-inorder-traversal/description/
描述:
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

解题思路
他的思路跟上面是一样的,此时他是要我们得到是中序遍历的结果,而中遍历的顺序是:左根右,因此我们放入栈的时机是左子树走完了才能放入到链表中,因此我们只需要将上面放入链表的代码,放到左子树为空的时候,并且我们要存放的第一个元素应该是我们此时左子树的最后一个结点,所以我们要放入的是此时的栈顶元素。
完整代码
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans = new LinkedList<>();if(root == null){return ans;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode tap = null;while(cur != null || !stack.isEmpty()){while(cur != null){stack.push(cur);cur = cur.left;}tap = stack.pop();ans.add(tap.val);cur = tap.right;}return ans;}
}
7、二叉树的后序遍历
习题链接https://leetcode.cn/problems/binary-tree-postorder-traversal/description/
https://leetcode.cn/problems/binary-tree-postorder-traversal/description/
描述:
给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。


解题思路
他的思路跟上面还是一样的,此时他是要我们得到是后序遍历的结果,而后遍历的顺序是:左右根,因此我们放入栈的时机是左子树走完了并且右子树也走完才能放入到链表中。
因此我们需要在我们的左子树为空时利用if语句判断,他的右子树是否为空,如果为空就出栈,并将原来的栈顶元素放入链表中,如果不为空就让cur等于此时栈顶元素的右边结点(注意:这里tap的赋值,不能用pop,而是用peek,因为,如果此时左子树为空的,但是右子树不为空,我们还需要继续往栈中入栈,因为后序遍历的顺序根左右根,我们需要将左子树和右子树走完才能打印根节点)
但是我们需要注意,在这样的情况下我们会陷入死循环,不断打印右子树最后的值,我们先来看下面

此时cur为空,tap等于结点7,tap.right 为空,此时我们就出栈并在链表中放入结点7,当时我们走完后栈不为空,再次进入循环此时cur为空,tap就等于结点5,但是结点5的右边为结点7不为空,cur 此时就等于结点7,然后cur此时就不为空,结点7,再次入栈,在这样的情况下我们发现我们会不停的打印结点7,陷入了死循环。
因此这时我们需要定义一个变量prev,让他存储弹出的结点,并在if判断中进行判断,如果此时的tap右边不为空但是他的右边等于我们弹出的元素我们就让他进入循环弹出此时栈顶元素,并放入链表中
例如:根据上面的图弹出7后,再次进入循环tap=5,此时tap.right为7不为空,但是tap.right为7等于我们的prev即弹出过的元素,就不往下走直接进入循环弹出结点5,这样就防止了死循环
完整代码
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> ans = new LinkedList<>();if(root == null){return ans;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode tap = null;TreeNode prev = null;while(cur != null || !stack.isEmpty()){while(cur != null){stack.push(cur);cur = cur.left;}tap = stack.peek();if(tap.right == null || tap.right == prev){stack.pop();ans.add(tap.val);prev = tap;}else{cur = tap.right;}} return ans;}
}
好了,今天的分享就到这里了,还请大家多多关注,我们下一篇见!
相关文章:
数据结构:二叉树—面试题(二)
1、二叉树的最近公共祖先 习题链接https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/ 描述: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点…...
【C++高并发服务器WebServer】-6:信号
本文目录 信号的概念1.1 core文件1.2 kill命令1.3 alarm函数1.4 setitimer调用1.5 signal捕捉信号1.6 信号集1.7 内核实现信号捕捉的过程1.8 sigaction1.9 sigchld 信号的概念 信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,…...
《探秘人工智能:从基础到未来变革》
在当今科技飞速发展的时代,人工智能(AI)无疑是最具影响力和变革性的技术之一。从手机里智能语音助手到自动驾驶汽车,从智能医疗诊断到智能金融服务,人工智能已经渗透到我们生活和工作的方方面面,悄然改变着…...
【数据分享】1929-2024年全球站点的逐月平均能见度(Shp\Excel\免费获取)
气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标!说到气象数据,最详细的气象数据是具体到气象监测站点的数据! 有关气象指标的监测站点数据,之前我们分享过1929-2024年全球气象站点…...
【PyTorch】3.张量类型转换
个人主页:Icomi 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架,为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术,能够处理复杂的数据模式。通过 PyTorch࿰…...
不解释快上车
聊一聊 最近有小伙伴问我有小红书图片和短视频下载的软件吗,我心想,下载那上面的图片和视频做什么?也许是自己没有这方面的需求,不了解。 不过话又说回来,有些很多下载器可能作者没有持续的维护,所以可能…...
C++红黑树详解
文章目录 红黑树概念规则为什么最长路径不超过最短路径的二倍?红黑树的时间复杂度红黑树的结构插入叔叔节点情况的讨论只变色(叔叔存在且为红)抽象的情况变色单旋(叔叔不存在或叔叔存在且为黑)变色双旋(叔叔不存在或叔叔存在且为黑…...
csapp2.4节——浮点数
目录 二进制小数 十进制小数转二进制小数 IEEE浮点表示 规格化表示 非规格化表示 特殊值 舍入 浮点运算 二进制小数 类比十进制中的小数,可定义出二进制小数 例如1010.0101 小数点后的权重从-1开始递减。 十进制小数转二进制小数 整数部分使用辗转相除…...
神经网络|(一)加权平均法,感知机和神经元
【1】引言 从这篇文章开始,将记述对神经网络知识的探索。相关文章都是学习过程中的感悟和理解,如有雷同或者南辕北辙的表述,请大家多多包涵。 【2】加权平均法 在数学课本和数理统计课本中,我们总会遇到求一组数据平均值的做法…...
Spring 框架:配置缓存管理器、注解参数与过期时间
在 Spring 框架中,可通过多种方式配置缓存具体行为,常见配置方法如下。 1. 缓存管理器(CacheManager)配置 基于内存的缓存管理器配置(以SimpleCacheManager为例) SimpleCacheManager 是 Spring 提供的简单…...
FPGA实现任意角度视频旋转(完结)视频任意角度旋转实现
本文主要介绍如何基于FPGA实现视频的任意角度旋转,关于视频180度实时旋转、90/270度视频无裁剪旋转,请见本专栏前面的文章,旋转效果示意图如下: 为了实时对比旋转效果,采用分屏显示进行处理,左边代表旋转…...
openlayer getLayerById 根据id获取layer图层
背景: 在项目中使用getLayerById获取图层,这个getLayerById()方法不是openlayer官方文档自带的,而是自己封装的一个方法,这个封装的方法的思路是:遍历所有的layer,根据唯一标识【可能是id,也可能…...
【Jave全栈】Java与JavaScript比较
文章目录 前言一、Java1、 历史与背景2、语言特点3、应用场景4、生态系统 二、JavaScript1、历史与背景2、语言特点3、应用场景4、 生态系统 三、相同点四、不同点1、语言类型2、用途3、语法和结构4、性能5、生态系统6、开发模式 前言 Java和JavaScript是两种不同的编程语言&a…...
设计模式-建造者模式、原型模式
目录 建造者模式 定义 类图 优缺点 角色 建造者模式和工厂模式比较 使用案例 原型模式 定义 类图 优缺点 应用场景 应用类型 浅克隆 深克隆 建造者模式 定义 将一个复杂的对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,…...
PTMD2.0-疾病相关的翻译后修饰数据库
翻译后修饰(PTMs,post-translational modifications)通过调节蛋白质功能参与了几乎所有的生物学过程,而 PTMs 的异常状态常常与人类疾病相关。在此,PTMD 2.0展示与疾病相关的 PTMs 综合数据库,其中包含 93 …...
【Git版本控制器--3】Git的远程操作
目录 理解分布式版本控制系统 创建远程仓库 仓库被创建后的配置信息 克隆远程仓库 https克隆仓库 ssh克隆仓库 向远程仓库推送 拉取远程仓库 忽略特殊文件 为什么要忽略特殊文件? 如何配置忽略特殊文件? 配置命令别名 标签管理 理…...
批量创建ES索引
7.x from elasticsearch import Elasticsearch# 配置 Elasticsearch 连接 # 替换为你的 Elasticsearch 地址、端口、用户名和密码 es Elasticsearch([http://10.10.x.x:43885],basic_auth(admin, XN272G9THEAPYD5N5QORX3PB1TSQELLB) )# # 测试连接 # try: # # 尝试获取集…...
模块初阶学习
当我们在过去想要实现一个功能时,例如Swap交换函数时,我们需要不断考虑参数的正确与否。如果是在c语言,我们还需要不断更改函数名字,以防止函数名重复。在c我们可以通过函数名重载解决这个问题,但还是有一些小问题&…...
rust学习-rust中的保留字
rust学习-rust中的保留字 已使用的保留字未来可能使用的保留字 保留字是语言中预定义的标识符,不能用作变量名、函数名或其他自定义标识符,Rust的保留字大致可以分为两类:已使用的保留字和未来可能使用的保留字 已使用的保留字 as࿱…...
MySQL中的读锁与写锁:概念与作用深度剖析
MySQL中的读锁与写锁:概念与作用深度剖析 在MySQL数据库的并发控制机制中,读锁和写锁起着至关重要的作用。它们是确保数据在多用户环境下能够正确、安全地被访问和修改的关键工具。 一、读锁(共享锁)概念 读锁,也称为…...
专利申请的价值
独占市场 一种产品只要授权专利权,等于在市场上拥有独占权。 政策奖励 各地方政府均出台响应文件, 对专利申请者进行奖励或者补助。 申报项目 申报高新技术企业、创新基金等 各类计划、项目的必要前提条件 专利申请 技术保护 防止新的技术与产品被他人 抄…...
使用 OpenCV 和 Python 轻松实现人脸检测
目录 一、准备工作 二、加载人脸检测模型 三、读取图像并进行人脸检测 四、处理视频中的人脸检测 五、优化人脸检测效果 六、总结 在人工智能和计算机视觉领域,人脸检测是一项非常基础且重要的技术。通过人脸检测,我们可以在图像或视频中识别并定位人脸,进而进行后续的…...
自然语言处理——从原理、经典模型到应用
1. 概述 自然语言处理(Natural Language Processing,NLP)是一门借助计算机技术研究人类语言的科学,是人工智能领域的一个分支,旨在让计算机理解、生成和处理人类语言。其核心任务是将非结构化的自然语言转换为机器可以…...
kotlin内联函数——runCatching
1.runCatching作用 代替try{}catch{}异常处理,用于捕获异常。 2.runCatching函数介绍 参数:上下文引用对象为参数返回值:lamda表达式结果 调用runCatching函数,如果调用成功则返回其封装的结果,并可回调onSuccess函…...
2025年新开局!谁在引领汽车AI风潮?
汽车AI革命已来。 在2025年伊始开幕的CES展上,AI汽车、AI座舱无疑成为了今年汽车行业的最大热点。其中不少车企在2025年CES上展示了其新一代AI座舱,为下一代智能汽车的人机交互、场景创新率先打样。 其中,东软集团也携带AI驱动、大数据支撑…...
YOLO目标检测3
一. 参考资料 《YOLO目标检测》 by 杨建华博士 本篇文章的主要内容来自于这本书,只是作为学习记录进行分享。 二. 搭建YOLOv1的网络 2.1 YOLOv1的网络结构 作者带我们构建的YOLOv1网络是一个全卷积结构,其中不包含任何全连接层,这一点可以…...
css3 svg制作404页面动画效果HTML源码
源码介绍 css3 svg制作404页面动画效果HTML源码,源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果 效果预览 源码如下 <!doctype html> <html> <head> <meta charse…...
LINUX 平台最快子网路由转发,内核使能选项配置
阅读本文之间,可线性参考以下文献。 Linux 命令行配置为单臂旁路由。_linux单臂路由-CSDN博客 Linux 软路由命令行配置(参考)_linux软路由-CSDN博客 VGW在 Windows 平台上局域网就绪的旁路由器程序_windows旁路由-CSDN博客 本文介绍 LINUX…...
「 机器人 」扑翼飞行器混合控制策略缺点浅谈
前言 将基于模型的控制与强化学习策略融合在扑翼飞行器中,虽然能够兼顾系统稳定性与极限机动能力,但也面临了更高的系统复杂性、对硬件算力与可靠性的额外要求,以及难以回避的能量效率等方面挑战。以下从四个方面进行归纳与分析。 1. 系统复杂性增加 1.1 两种控制方法的并存…...
RNN实现阿尔茨海默症的诊断识别
本文为为🔗365天深度学习训练营内部文章 原作者:K同学啊 一 导入数据 import torch.nn as nn import torch.nn.functional as F import torchvision,torch from sklearn.preprocessing import StandardScaler from torch.utils.data import TensorDatase…...
