面试经典算法系列之二叉树1 -- 从前序与中序遍历序列构造二叉树

面试经典算法16 - 从前序与中序遍历序列构造二叉树

LeetCode.105
公众号:阿Q技术站

问题描述

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

img

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorderinorder无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

思路

递归

构造二叉树的基本思路是利用先序遍历和中序遍历的特点。先序遍历的顺序是根节点、左子树、右子树;中序遍历的顺序是左子树、根节点、右子树。

  1. 首先,先序遍历的第一个元素一定是根节点的值。
  2. 然后,在中序遍历中找到这个根节点的位置,它左边的元素都是左子树的节点,右边的元素都是右子树的节点。
  3. 根据中序遍历中根节点的位置,可以得到左子树和右子树的节点数目,进而可以在先序遍历中找到左子树和右子树的分界点。
  4. 递归地构建左子树和右子树。
非递归
  1. 创建根节点并将其压入栈中。

  2. 初始化前序遍历索引 preIndex 为 0,表示当前处理的是前序遍历的第一个元素(根节点)。

  3. 循环执行以下步骤,直到栈为空:

    a. 从栈中弹出一个节点作为当前节点。

    b. 如果当前节点的值不等于中序遍历中的当前值(由 inIndex 指示),则将当前节点的右子节点设置为前序遍历中的下一个值,并将右子节点压入栈中。

    c. 否则,更新 inIndex 指示下一个中序遍历的值,并将当前节点的左子节点设置为前序遍历中的下一个值,然后将左子节点压入栈中。

  4. 返回根节点。

图解

今天给大家画一个相对简单好理解的流程图。

          3/   \9    20/  \15    7preorder:  [3, 9, 20, 15, 7]
inorder:   [9, 3, 15, 20, 7]1. 创建根节点 root = TreeNode(3)
2. 将 root 压入栈 s 中
3. preIndex = 1, inIndex = 0
4. 进入循环,preIndex < preorder.size()- currNode = s.top() = root- root->val != inorder[0] (9)- root->left = TreeNode(9)- 将 root->left 压入栈 s 中- preIndex = 2
5. preIndex = 2, inIndex = 0
6. 进入循环,preIndex < preorder.size()- currNode = s.top() = root->left (9)- root->left->val != inorder[1] (3)- root->left->left = TreeNode(20)- 将 root->left->left 压入栈 s 中- preIndex = 3
7. preIndex = 3, inIndex = 0
8. 进入循环,preIndex < preorder.size()- currNode = s.top() = root->left->left (20)- root->left->left->val != inorder[2] (15)- root->left->left->left = TreeNode(15)- 将 root->left->left->left 压入栈 s 中- preIndex = 4
9. preIndex = 4, inIndex = 0
10. 进入循环,preIndex < preorder.size()- currNode = s.top() = root->left->left->left (15)- root->left->left->left->val != inorder[3] (20)- root->left->left->left->right = TreeNode(7)- 将 root->left->left->left->right 压入栈 s 中- preIndex = 5
11. preIndex = 5, inIndex = 0
12. 进入循环,preIndex < preorder.size()- preIndex == preorder.size(),跳出循环
13. 返回根节点 root

参考代码

C++
递归
#include <iostream>
#include <vector>
#include <unordered_map>using namespace std;// 二叉树节点的定义
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};class Solution {
public:unordered_map<int, int> index_map; // 用于存储中序遍历中节点值和索引的映射TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {// 构建中序遍历中节点值和索引的映射for (int i = 0; i < inorder.size(); ++i) {index_map[inorder[i]] = i;}return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1);}TreeNode* build(vector<int>& preorder, vector<int>& inorder, int pre_left, int pre_right, int in_left, int in_right) {if (pre_left > pre_right || in_left > in_right) {return nullptr; // 如果前序遍历或中序遍历的起始位置大于结束位置,返回空节点}int root_val = preorder[pre_left]; // 当前子树的根节点值TreeNode* root = new TreeNode(root_val); // 创建当前子树的根节点// 在中序遍历中找到根节点的位置int index = index_map[root_val];int left_size = index - in_left; // 左子树的节点数目// 递归构建左子树和右子树root->left = build(preorder, inorder, pre_left + 1, pre_left + left_size, in_left, index - 1);root->right = build(preorder, inorder, pre_left + left_size + 1, pre_right, index + 1, in_right);return root; // 返回当前子树的根节点}
};// 打印二叉树的先序遍历结果
void printPreorder(TreeNode* root) {if (!root) {return;}cout << root->val << " ";printPreorder(root->left);printPreorder(root->right);
}int main() {vector<int> preorder = {3, 9, 20, 15, 7}; // 二叉树的先序遍历序列vector<int> inorder = {9, 3, 15, 20, 7};  // 二叉树的中序遍历序列Solution solution;TreeNode* root = solution.buildTree(preorder, inorder); // 构建二叉树printPreorder(root); // 输出二叉树的先序遍历结果cout << endl;return 0;
}
非递归
#include <iostream>
#include <vector>
#include <stack>
#include <unordered_map>using namespace std;// 二叉树节点的定义
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};class Solution {
public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {if (preorder.empty()) {return nullptr; // 如果前序遍历序列为空,返回空指针}TreeNode* root = new TreeNode(preorder[0]); // 创建根节点stack<TreeNode*> s; // 辅助栈,用于模拟递归过程s.push(root); // 将根节点压入栈中int preIndex = 1; // 前序遍历索引,从第二个元素开始int inIndex = 0; // 中序遍历索引,从第一个元素开始while (preIndex < preorder.size()) {TreeNode* currNode = s.top(); // 获取栈顶节点作为当前节点if (currNode->val != inorder[inIndex]) {// 如果当前节点的值不等于中序遍历中的当前值,说明还有左子树需要处理currNode->left = new TreeNode(preorder[preIndex++]); // 创建左子节点s.push(currNode->left); // 将左子节点压入栈中} else {// 如果当前节点的值等于中序遍历中的当前值,说明当前节点是中序遍历的根节点while (!s.empty() && s.top()->val == inorder[inIndex]) {// 依次弹出栈顶节点,直到栈为空或栈顶节点的值不等于中序遍历的当前值currNode = s.top();s.pop();++inIndex; // 更新中序遍历索引,指向下一个节点}currNode->right = new TreeNode(preorder[preIndex++]); // 创建右子节点s.push(currNode->right); // 将右子节点压入栈中}}return root; // 返回根节点}
};// 打印二叉树的先序遍历结果
void printPreorder(TreeNode* root) {if (!root) {return;}cout << root->val << " ";printPreorder(root->left);printPreorder(root->right);
}int main() {vector<int> preorder = {3, 9, 20, 15, 7}; // 二叉树的先序遍历序列vector<int> inorder = {9, 3, 15, 20, 7};  // 二叉树的中序遍历序列Solution solution;TreeNode* root = solution.buildTree(preorder, inorder); // 构建二叉树printPreorder(root); // 输出二叉树的先序遍历结果cout << endl;return 0;
}
Java
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;// 二叉树节点的定义
class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) { val = x; }
}class Solution {public TreeNode buildTree(int[] preorder, int[] inorder) {if (preorder.length == 0) {return null; // 如果前序遍历序列为空,返回空节点}TreeNode root = new TreeNode(preorder[0]); // 创建根节点Stack<TreeNode> stack = new Stack<>(); // 辅助栈,用于模拟递归过程stack.push(root); // 将根节点压入栈中int preIndex = 1; // 前序遍历索引,从第二个元素开始int inIndex = 0; // 中序遍历索引,从第一个元素开始while (preIndex < preorder.length) {TreeNode currNode = stack.peek(); // 获取栈顶节点作为当前节点if (currNode.val != inorder[inIndex]) {// 如果当前节点的值不等于中序遍历中的当前值,说明还有左子树需要处理currNode.left = new TreeNode(preorder[preIndex++]); // 创建左子节点stack.push(currNode.left); // 将左子节点压入栈中} else {// 如果当前节点的值等于中序遍历中的当前值,说明当前节点是中序遍历的根节点while (!stack.isEmpty() && stack.peek().val == inorder[inIndex]) {// 依次弹出栈顶节点,直到栈为空或栈顶节点的值不等于中序遍历的当前值currNode = stack.pop();inIndex++; // 更新中序遍历索引,指向下一个节点}currNode.right = new TreeNode(preorder[preIndex++]); // 创建右子节点stack.push(currNode.right); // 将右子节点压入栈中}}return root; // 返回根节点}
}public class Main {public static void main(String[] args) {int[] preorder = {3, 9, 20, 15, 7}; // 二叉树的先序遍历序列int[] inorder = {9, 3, 15, 20, 7}; // 二叉树的中序遍历序列Solution solution = new Solution();TreeNode root = solution.buildTree(preorder, inorder); // 构建二叉树printPreorder(root); // 输出二叉树的先序遍历结果System.out.println();}private static void printPreorder(TreeNode root) {if (root == null) {return;}System.out.print(root.val + " ");printPreorder(root.left);printPreorder(root.right);}
}
Python
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:if not preorder:return Noneroot = TreeNode(preorder[0])  # 创建根节点stack = [root]  # 辅助栈,用于模拟递归过程preIndex, inIndex = 1, 0  # 前序遍历索引从第二个元素开始,中序遍历索引从第一个元素开始while preIndex < len(preorder):currNode = stack[-1]  # 获取栈顶节点作为当前节点if currNode.val != inorder[inIndex]:# 如果当前节点的值不等于中序遍历中的当前值,说明还有左子树需要处理currNode.left = TreeNode(preorder[preIndex])  # 创建左子节点stack.append(currNode.left)  # 将左子节点压入栈中preIndex += 1  # 更新前序遍历索引else:# 如果当前节点的值等于中序遍历中的当前值,说明当前节点是中序遍历的根节点while stack and stack[-1].val == inorder[inIndex]:# 依次弹出栈顶节点,直到栈为空或栈顶节点的值不等于中序遍历的当前值currNode = stack.pop()inIndex += 1  # 更新中序遍历索引currNode.right = TreeNode(preorder[preIndex])  # 创建右子节点stack.append(currNode.right)  # 将右子节点压入栈中preIndex += 1  # 更新前序遍历索引return root  # 返回根节点

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

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

相关文章

VUE typescript 调用stompjs[Rabbit MQ]

npm拉下来最新的2.3.9版本&#xff0c;发现一些原来Js代码已经不能用了。顺便解读了下最新定义的内容 // <reference types"node" />export const VERSIONS: {V1_0: string;V1_1: string;V1_2: string;supportedVersions: () > string[]; };export class C…

airtest-ios真机搭建实践

首先阅读4 ios connection - Airtest Project Docs 在Windows环境下搭建Airtest对iOS真机进行自动化测试的过程相对复杂&#xff0c;因为iOS的自动化测试通常需要依赖Mac OS系统&#xff0c;但理论上借助一些工具和服务&#xff0c;Windows用户也可以间接完成部分工作。下面是…

前端对接fastGPT流式数据+打字机效果

首先在对接api时 参数要设置stream: true, const data {chatId: abc,stream: true,//这里true返回流式数据detail: false,variables: {uid: sfdsdf,name: zhaoyunyao,},messages: [{ content: text, role: user }]}; 不要用axios发请求 不然处理不了流式数据 我这里使用fetch …

PairAug:增强图像-文本对对放射学有什么用?

论文链接 代码链接GitHub - YtongXie/PairAug: [CVPR2024] PairAug: What Can Augmented Image-Text Pairs Do for Radiology? 发表于CVPR2024 机构 1) 澳大利亚机器学习研究所(AIML)&#xff0c;澳大利亚阿德莱德大学 2) 西北工业大学计算机科学与工程学院 3) 西北工业…

【简明图文教程】Node.js的下载、安装、环境配置及测试

文章目录 前言下载Node.js安装Node.js配置Node.js配置环境变量测试后言 前言 本教程适用于小白第一次从零开始进行Node.js的下载、安装、环境配置及测试。 如果你之前已经安装过了Node.js或删除掉了Node.js想重新安装&#xff0c;需要先参考以下博客进行处理后&#xff0c;再根…

渗透测试实战——第一站

仅供交流学习使用&#xff0c;请勿用于非法用途 前言&#xff1a;刚学了sql注入&#xff0c;只听理论总感觉没啥用&#xff0c;今天花了一半个多小时&#xff0c;去尝试寻找有漏洞的网站&#xff0c;最终找到了一个&#xff1b;实践是检验真理的唯一标准。 我是通过黑客常用语法…

牛客 NC36 在两个长度相等的排序数组中找到上中位数【中等 模拟 Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/6fbe70f3a51d44fa9395cfc49694404f 思路 直接模拟2个数组有顺序放到一个数组中&#xff0c;然后返回中间的数参考答案java import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 pu…

深入剖析UDP反射放大攻击原理及其有效防护策略

引言 随着互联网的飞速发展和业务复杂性的提升&#xff0c;网络安全问题日益凸显&#xff0c;其中分布式拒绝服务&#xff08;DDoS&#xff09;攻击成为危害最为严重的一类网络威胁之一。UDP反射放大攻击作为一种高效的DDoS手段&#xff0c;因其攻击成本低廉、威力巨大&#x…

IDEA中无法保存设置 Cannot Save Settings

确定原因: 在IDEA中父工程不应该存在有子工程的相关东西 首先,这是我的DCYJ项目(观察右侧的Content Root) 其次,这是我的EAPOFode项目(观察右侧的Content Root爆红处) 最后我将DCYJ项目右侧的Content Root全部删掉

关于部署ELK和EFLKD的相关知识

文章目录 一、ELK日志分析系统1、ELK简介1.2 ElasticSearch1.3 Logstash1.4 Kibana&#xff08;展示数据可视化界面&#xff09;1.5 Filebeat 2、使用ELK的原因3、完整日志系统的基本特征4、ELK的工作原理 二、部署ELK日志分析系统1、服务器配置2、关闭防火墙3、ELK ElasticSea…

ASUS华硕ROG幻16Air笔记本电脑GU605M原装出厂Win11系统工厂包下载,带有ASUSRecovery一键重置还原

适用型号&#xff1a;GU605MI、GU605MY、GU605MZ、GU605MV、GU605MU 链接&#xff1a;https://pan.baidu.com/s/1YBmZZbTKpIu883jYCS9KfA?pwd9jd4 提取码&#xff1a;9jd4 华硕原厂Windows11系统带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系统属性联机支持…

Java后端搭建流程

目录 一、后端开发准备工作 1.下载 2.安装jdk &#xff0c;配置JAVA-HOME path 3.启动Tomcat 4.访问ip和端口 二、创建web项目 1.新建一个项目 2.发布web应用到服务器 &#xff08;1&#xff09;对LoginServlet继承HttpServlet &#xff08;2&#xff09;重写父类方法…

如何让视频流媒体平台免受网络攻击

在各国&#xff0c;流媒体服务已越来越受到大众的欢迎。有统计表明&#xff0c;目前视频流已占网络整体流量的80%以上。不过如您所见&#xff0c;近年来&#xff0c;数字威胁的不断增加&#xff0c;也让网络攻击逐年递增。单个视频用户受到的危险&#xff0c;往往会危及到整个服…

Vue 项目build打包发布到giteepages ,首页正常显示,其他路由页面报错404的解决方法

直接上解决方法&#xff1a; 打包之后dist上传之后&#xff0c;还要新创一个.spa文件&#xff0c;注意&#xff01;是 .spa 有个. 点&#xff0c;如下图 一般这样就可以开始部署了&#xff0c;然后开启giteepages服务。如果出现了首页正常显示&#xff0c;其他页面显示…

全新华为MateBook X Pro发布,将Ultra9放入980g超轻薄机身

2024年4月11日&#xff0c;在华为鸿蒙生态春季沟通会上全新的华为MateBook X Pro正式发布。该机以美学设计、创新科技以及智慧体验&#xff0c;追求重新定义Pro、重新定义旗舰&#xff0c;将颠覆消费者对传统轻薄本的认知。 华为MateBook X Pro追求极致轻薄与强大性能的完美结合…

Java安全管理器-SecurityManager

定义&#xff1a; SecurityManager是Java中的一个类&#xff0c;用于实现安全管理功能。它允许应用程序在运行时对安全策略进行动态管理&#xff0c;并控制哪些操作可以执行&#xff0c;哪些应该被拒绝。主要功能包括&#xff1a; 安全策略管理&#xff1a;SecurityManager允许…

D-LinkNAS 远程命令执行漏洞(CVE-2024-3273)RCE漏

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 D-LinkNAS是由D-Link公司制造的网络附加存储设备。…

SOLIDOWRKS怎么将中间格式的模具装配体转化为装配体格式

模具是工业生产中用于制作成型物品的工具&#xff0c;它由各种零件构成&#xff0c;可以通过改变所成型材料的物理状态来实现物品外形的加工。如果工程师已经有其他格式的模具装配体&#xff0c;但是又想将其他格式的模具装配体导入solidworks里面&#xff0c;并且将一个个实体…

数字人项目 ER-NeRF 的使用和部署详细教程

文章目录 1. ER-NeRF简介2. ER-NeRF部署3. 训练自己的数字人4. 生成数字人视频5. 其他数字人模型比较常见错误 1. ER-NeRF简介 ER-NeRF&#xff08;官方链接&#xff09;是一个Talking Portrait Synthesis&#xff08;对嘴型&#xff09;项目。即&#xff1a;给一段某人说话的…

Linux网络基础2(下)

传输层 再谈端口号端口号的划分netstatpidof UDP协议 UDP的特点UDP缓冲区UDP使用注意事项UDP报头的理解基于UDP的应用层协议 TCP协议 4位首部长度16位窗口大小确认应答机制32位序号和32位确认序号6个标记位超时重传机制连接管理机制流量控制快重传机制再谈序号延迟应答面相字节…