二叉搜索树题目:前序遍历构造二叉搜索树

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法三
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法四
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:前序遍历构造二叉搜索树

出处:1008. 前序遍历构造二叉搜索树

难度

5 级

题目描述

要求

给定一个表示二叉搜索树的前序遍历的整数数组,构造树并返回其根结点。

保证对于给定的测试用例,总是存在符合要求的二叉搜索树。

示例

示例 1:

示例 1

输入: preorder = [8,5,1,7,10,12] \texttt{preorder = [8,5,1,7,10,12]} preorder = [8,5,1,7,10,12]
输出: [8,5,10,1,7,null,12] \texttt{[8,5,10,1,7,null,12]} [8,5,10,1,7,null,12]

示例 2:

输入: preorder = [1,3] \texttt{preorder = [1,3]} preorder = [1,3]
输出: [1,null,3] \texttt{[1,null,3]} [1,null,3]

数据范围

  • 1 ≤ preorder.length ≤ 100 \texttt{1} \le \texttt{preorder.length} \le \texttt{100} 1preorder.length100
  • 1 ≤ preorder[i] ≤ 1000 \texttt{1} \le \texttt{preorder[i]} \le \texttt{1000} 1preorder[i]1000
  • preorder \texttt{preorder} preorder 中的值各不相同

解法一

思路和算法

由于二叉搜索树的中序遍历序列是单调递增的,因此将给定的前序遍历序列排序之后即可得到二叉搜索树的中序遍历序列。在已知前序遍历序列和中序遍历序列的情况下,可以使用「从前序与中序遍历序列构造二叉树」的做法构造二叉搜索树。

根据前序遍历序列和中序遍历序列的信息,可以使用递归分治的方法构造二叉搜索树。

前序遍历序列的第一个元素值为根结点值,只要在中序遍历序列中定位到根结点值的下标,即可得到左子树中的结点数和右子树中的结点数。对于左子树和右子树,也可以在给定的前序遍历序列和中序遍历序列中分别得到对应的子序列,根据子序列构造相应的子树。

代码

class Solution {Map<Integer, Integer> inorderIndices = new HashMap<Integer, Integer>();int[] preorder;int[] inorder;public TreeNode bstFromPreorder(int[] preorder) {this.preorder = preorder;int length = preorder.length;this.inorder = new int[length];System.arraycopy(preorder, 0, inorder, 0, length);Arrays.sort(inorder);for (int i = 0; i < length; i++) {inorderIndices.put(inorder[i], i);}return buildTree(0, 0, length);}public TreeNode buildTree(int preorderStart, int inorderStart, int nodesCount) {if (nodesCount == 0) {return null;}int rootVal = preorder[preorderStart];TreeNode root = new TreeNode(rootVal);int inorderRootIndex = inorderIndices.get(rootVal);int leftNodesCount = inorderRootIndex - inorderStart;int rightNodesCount = nodesCount - 1 - leftNodesCount;root.left = buildTree(preorderStart + 1, inorderStart, leftNodesCount);root.right = buildTree(preorderStart + 1 + leftNodesCount, inorderRootIndex + 1, rightNodesCount);return root;}
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。得到中序遍历序列需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间,构造二叉搜索树需要 O ( n ) O(n) O(n) 的时间,因此时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。空间复杂度主要是递归调用的栈空间以及哈希表空间,因此空间复杂度是 O ( n ) O(n) O(n)

解法二

思路和算法

在已知前序遍历序列和中序遍历序列的情况下,也可以使用迭代的方法构造二叉搜索树。遍历前序遍历序列,对于每个值分别创建结点,使用栈存储结点,并利用中序遍历序列的信息得到遍历序列中相邻结点之间的关系,即可构造二叉搜索树。

代码

class Solution {public TreeNode bstFromPreorder(int[] preorder) {int length = preorder.length;int[] inorder = new int[length];System.arraycopy(preorder, 0, inorder, 0, length);Arrays.sort(inorder);TreeNode root = new TreeNode(preorder[0]);Deque<TreeNode> stack = new ArrayDeque<TreeNode>();stack.push(root);int inorderIndex = 0;for (int i = 1; i < length; i++) {TreeNode prev = stack.peek();TreeNode curr = new TreeNode(preorder[i]);if (prev.val != inorder[inorderIndex]) {prev.left = curr;} else {while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {prev = stack.pop();inorderIndex++;}prev.right = curr;}stack.push(curr);}return root;}
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。得到中序遍历序列需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间,前序遍历序列和中序遍历序列各需要遍历一次,构造二叉搜索树需要 O ( n ) O(n) O(n) 的时间,因此时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。空间复杂度主要是栈空间,因此空间复杂度是 O ( n ) O(n) O(n)

解法三

思路和算法

解法一和解法二的做法是首先得到二叉搜索树的中序遍历序列,然后根据前序遍历序列和中序遍历序列构造二叉搜索树。其实,并不需要中序遍历序列,根据前序遍历序列即可构造二叉搜索树。

由于前序遍历序列的第一个元素值为根结点值,因此首先创建根结点,然后遍历前序遍历序列的其余元素值,对于每个值分别创建结点并插入二叉搜索树中,当遍历结束时所有结点都插入二叉搜索树中,二叉搜索树构造完毕。

由于插入结点的顺序和前序遍历的顺序相同,每次插入的结点一定是一个已有的结点的子结点,因此每次插入的结点在前序遍历序列中的位置一定在已有的结点之后,通过插入操作得到的二叉搜索树一定符合给定的前序遍历序列。

代码

class Solution {public TreeNode bstFromPreorder(int[] preorder) {TreeNode root = new TreeNode(preorder[0]);int length = preorder.length;for (int i = 1; i < length; i++) {insert(root, preorder[i]);}return root;}public void insert(TreeNode root, int val) {TreeNode insertNode = new TreeNode(val);TreeNode node = root, parent = null;while (node != null) {parent = node;if (node.val > val) {node = node.left;} else {node = node.right;}}if (parent.val > val) {parent.left = insertNode;} else {parent.right = insertNode;}}
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。需要执行 n n n 次插入操作,每次插入操作的时间复杂度取决于二叉搜索树的高度,最坏情况下二叉搜索树的高度是 O ( n ) O(n) O(n),此时总时间复杂度是 O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度: O ( 1 ) O(1) O(1)。注意返回值不计入空间复杂度。

解法四

思路和算法

解法一和解法二利用了二叉搜索树的中序遍历序列单调递增的性质,但是由于需要排序,因此时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)。在不排序的情况下利用单调性,则可以将时间复杂度降低到 O ( n ) O(n) O(n)

对于前序遍历序列中的两个相邻的值 x x x y y y,其对应结点的关系一定是以下两种情况之一:

  1. 如果 x > y x > y x>y,则结点 y y y 是结点 x x x 的左子结点;

  2. 如果 x < y x < y x<y,则结点 x x x 没有左子结点,结点 y y y 是结点 x x x 的右子结点,或者结点 y y y 是结点 x x x 的某个祖先结点的右子结点。

由于可以根据结点值的大小关系判断是两种情况的哪一种,因此可以使用单调栈实现构造二叉搜索树。单调栈存储结点,满足从栈底到栈顶的结点值单调递减。

由于前序遍历序列的第一个元素值为根结点值,因此首先创建根结点并将根结点入栈,然后遍历前序遍历序列的其余元素值并构造二叉搜索树。对于每个值,创建结点,然后比较当前值与栈顶结点值的大小,执行如下操作。

  • 如果栈顶结点值大于当前值,则是第 1 种情况,即当前结点是栈顶结点的左子结点,因此将当前结点作为栈顶结点的左子结点,然后将当前结点入栈。

  • 如果栈顶结点值小于当前值,则是第 2 种情况,即当前结点是栈顶结点或者栈顶结点的某个祖先结点的右子结点,因此将结点依次出栈,直到栈为空或者栈顶结点值大于当前值,将当前结点作为最后一个出栈结点的右子结点,然后将当前结点入栈。

当遍历结束时,二叉搜索树构造完毕。

上述操作中,初始时根结点已经入栈,每次创建结点之后都会将结点入栈,因此遍历到每个值的时候都可以确保栈不为空。

考虑示例 1 的前序遍历构造二叉搜索树,前序遍历是 [ 8 , 5 , 1 , 7 , 10 , 12 ] [8,5,1,7,10,12] [8,5,1,7,10,12]

  1. 创建结点 8 8 8 作为根结点,将根结点入栈, stack = [ 8 ] \textit{stack} = [8] stack=[8],其中左边为栈底,右边为栈顶,栈内的结点用结点值表示。

  2. 创建结点 5 5 5,由于 8 > 5 8 > 5 8>5,因此将结点 5 5 5 作为结点 8 8 8 的左子结点,将结点 5 5 5 入栈, stack = [ 8 , 5 ] \textit{stack} = [8, 5] stack=[8,5]

  3. 创建结点 1 1 1,由于 5 > 1 5 > 1 5>1,因此将结点 1 1 1 作为结点 5 5 5 的左子结点,将结点 1 1 1 入栈, stack = [ 8 , 5 , 1 ] \textit{stack} = [8, 5, 1] stack=[8,5,1]

  4. 创建结点 7 7 7,由于 1 < 7 1 < 7 1<7 5 < 7 5 < 7 5<7,因此将结点 1 1 1 和结点 5 5 5 出栈,将结点 7 7 7 作为结点 5 5 5 的右子结点,将结点 7 7 7 入栈, stack = [ 8 , 7 ] \textit{stack} = [8, 7] stack=[8,7]

  5. 创建结点 10 10 10,由于 7 < 10 7 < 10 7<10 8 < 10 8 < 10 8<10,因此将结点 7 7 7 和结点 8 8 8 出栈,将结点 10 10 10 作为结点 8 8 8 的右子结点,将结点 10 10 10 入栈, stack = [ 10 ] \textit{stack} = [10] stack=[10]

  6. 创建结点 12 12 12,由于 10 < 12 10 < 12 10<12,因此将结点 10 10 10 出栈,将结点 12 12 12 作为结点 10 10 10 的右子结点,将结点 12 12 12 入栈, stack = [ 12 ] \textit{stack} = [12] stack=[12]

此时遍历结束,二叉搜索树构造完毕。

代码

class Solution {public TreeNode bstFromPreorder(int[] preorder) {TreeNode root = new TreeNode(preorder[0]);Deque<TreeNode> stack = new ArrayDeque<TreeNode>();stack.push(root);int length = preorder.length;for (int i = 1; i < length; i++) {int val = preorder[i];TreeNode node = new TreeNode(val);if (stack.peek().val > val) {stack.peek().left = node;} else {TreeNode prev = null;while (!stack.isEmpty() && stack.peek().val < val) {prev = stack.pop();}prev.right = node;}stack.push(node);}return root;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。需要执行 n n n 次插入操作,每个结点最多入栈和出栈各一次,因此时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder 的长度,即二叉搜索树的结点数。空间复杂度主要取决于栈空间,栈内结点个数不会超过 n n n

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

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

相关文章

【解决】Sublime Text找不到Package Control选项,且输入install也不显示Install Package(其中一种情况)

【问题描述】 Sublime Text 找不到 Package Control 选项&#xff0c;且输入 install 也不显示 Install Package 【解决方法】&#xff08;其中一种情况&#xff09; 1、工具栏 Preferences -> Settings&#xff0c;点开查看设置文档 2、检查 "ignored_packages&q…

提示并输入一个字符串,统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C++风格字符串完成

#include <iostream> #include <array> using namespace std;int main() {cout<<"请输入一个字符串"<<endl;//array<string,100> str;string str;getline(cin,str);int daxie0,xiaoxie0,num0,space0,other0;int lenstr.size();;for(in…

使用sunny-Ngrok免费实现内网穿透

1、注册用户 网址&#xff1a;https://ngrok.cc/login/register 2、实名认证 注册成功之后&#xff0c;登录系统&#xff0c;进行实名认证&#xff0c;认证费两元。认证通过后才能开通隧道。 3、开通隧道 选择免费的隧道 4、开通成功后查看开通隧道 5、启动隧道 下载…

日常002:双系统时间不一致问题

日常002&#xff1a;双系统时间不一致问题 推荐解决方法&#xff1a;Windows管理员执行如下命令&#xff0c;将硬件时钟设置为UTC时间 reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWO…

计算机网络—以太网接口和链路配置

目录 1.拓扑图 2.以太网交换机基础配置 3.配置手动模式的链路聚合 4.配置静态 LACP 模式的链路聚合 5.配置文件 1.拓扑图 2.以太网交换机基础配置 华为交换机接口默认开启了自协商功能&#xff0c;需要手动配置S1与 S2上G0/0/9和G0/0/10接口的速率。 首先修改交换机的设…

力扣--动态规划97.交错字符串

思路分析&#xff1a; 动态规划数组定义&#xff1a; dp[i][j] 表示&#xff1a;使用字符串 s1 的前 i 个字符和字符串 s2 的前 j 个字符&#xff0c;能否构成字符串 s3 的前 i j 个字符的交错组合。 初始化&#xff1a; dp[0][0] 初始化为 1&#xff0c;表示空串是 s1 和 s2 …

蓝桥杯[OJ 2928]分糖果-CPP(贪心、字典序)

目录 一、题目描述&#xff1a; 二、整体思路 (一)字典序比较规则 (二)正确理解题意 &#xff08;三&#xff09;分类讨论 三、代码 一、题目描述&#xff1a; 二、整体思路 (一)字典序比较规则 首先要知道字典序是怎么比较大小的&#xff0c;简单来说按以下次序进行比较&am…

再也不想去字节跳动面试了,6年测开面试遭到这样打击.....

前几天我朋友跟我吐苦水&#xff0c;这波面试又把他打击到了&#xff0c;做了快6年软件测试员。。。为了进大厂&#xff0c;也花了很多时间和精力在面试准备上&#xff0c;也刷了很多题。但题刷多了之后有点怀疑人生&#xff0c;不知道刷的这些题在之后的工作中能不能用到&…

权限管理系统-0.4.0

五、权限管理 5.1 引入JWT JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以…

C语言基础练习——Day05

目录 选择题 编程题 数字在升序数组中出现的次数 整数转换 选择题 1、如下程序的功能是 #include <stdio.h> int main() {char ch[80] "123abcdEFG*&";int j;puts(ch);for(j 0; ch[j] ! \0; j){if(ch[j] > A && ch[j] < Z)ch[j] ch[j] e…

智能测径仪在胶管行业的应用

关键字&#xff1a;胶管外径尺寸测量&#xff0c;胶管检测仪器&#xff0c;胶管外径检测&#xff0c;高温胶管外径检测&#xff0c;软硬胶管检测&#xff0c; 智能测径仪在家胶管行业中的应用主要体现在对胶管外径的精确测量和控制上。在胶管生产过程中&#xff0c;外径的大小直…

dubbo调用的自定义过滤器中设置MDC无法生效的问题

AI的解释 Dubbo自定义过滤器不生效可能有多种原因&#xff0c;以下是一些常见的原因及解决方法&#xff1a; 过滤器未正确配置&#xff1a; 检查过滤器是否已经在Dubbo的配置文件中正确声明&#xff0c;并且已经添加到过滤器链中。在XML配置中&#xff0c;应使用<dubbo:se…

Python3虚拟环境之pipenv

pipenv是python官方推荐的包管理工具&#xff0c;集成了virtualenv, pip和pyenv三者的功能。集合了所有的包管理工具的长处&#xff0c;自动为项目创建和管理虚拟环境。 安装 pip install pipenv在Pycharm中使用 修改Pipfile的安装源参数url&#xff0c;改为https://pypi.tun…

Valid8Proxy:一款功能强大的工作代理获取、验证和存储工具

关于Valid8Proxy Valid8Proxy是一款功能强大且用户友好的代理管理工具&#xff0c;该工具功能丰富&#xff0c;旨在帮助广大研究人员获取、验证和存储工作代理的相关信息。 无论你是需要用于网络资源爬取、网络数字匿名化还是测试网络安全的代理&#xff0c;Valid8Proxy都可以…

应用方案 |安防摄像头(IPC)的步进马达及IR-CUT驱动芯片D6212

应用领域 安防摄像头&#xff08;IPC&#xff09;的步进马达及IR-CUT驱动。 02 功能介绍 D6212内置8路带有续流二极管的达林顿驱动管阵列和一个H桥驱动&#xff0c;单芯片即可实现2个步进电机和一个IR-CUT的直接驱动&#xff0c;使得电路应用非常简单。单个达林顿管在输入…

java异常概述及自定义处理

前言 学到异常了&#xff0c;本来以为处理异常只是避免bug&#xff0c;结果发现还可以为了编程需要自定义异常。打好基础&#xff0c;daydayup! 异常 什么是异常 异常就是代表程序出现的问题 异常的体系 异常体系指的是java开发人员为了方便程序员使用所开发的异常类&#xff…

【喜报!】科大睿智为企业成功通过CMMI5级评估!

山东智云信息科技有限公司成立于2011年&#xff0c;总部地处泉城济南&#xff0c;一直专注于生态环境信息化领域解决方案的咨询设计、产品研发、项目实施和系统集成类服务&#xff0c;致力于成为固定污染源监管与非现场精准执法领域的领军企业。 山东智云拥有100余名生态环境信…

C++ 作业 24/3/11

1、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数&#xff08;要求使用C风格字符串完成&#xff09; #include <iostream>using namespace std;int main() {string str;cout << "please enter str:&…

0基础、适合转行学Python吗?

01 对于0基础的人&#xff0c;直接学 Python 编程合适吗&#xff1f; 在目前的编程语言中&#xff0c;Python的抽象程度是最高的&#xff0c;是最接近自然语言的&#xff0c;非常容易上手&#xff0c;Python 可以让你更好的理解编程这件事情。 所以&#xff0c;我只能说非常…

最新免费好用的手机数据恢复软件EasyRecovery易恢复 2024

easyrecovery pro在easyrecovery的基础上进行了全新的升级&#xff0c;对于可恢复的内容进行了深度的开发&#xff0c;easyrecovery pro相比较旧版的easyrecovery而言&#xff0c;可恢复的选项更多&#xff0c;时间跨度也更长。easyrecovery pro简介&#xff1a;1、非常强大的数…