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

news2025/1/6 18:50:18

文章目录

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

题目

标题和出处

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

出处: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.coloradmin.cn/o/1508863.html

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

相关文章

2024年春招程序员个人简历范本(精选5篇|附模板)

HR浏览一份简历也就25秒左右,如果你连「好简历」都没有,怎么能找到好工作呢? 如果你不懂得如何在简历上展示自己,或者觉得怎么改简历都不出彩,那请你一定仔细读完。 Java开发工程师简历范本> 性别 男 年龄 24 学历 本科 张三 专业 计算机科学与技术 毕业院校 …

Python3虚拟环境之pipenv

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

20240309web前端_第一周作业_古诗词

作业三&#xff1a;古诗词 成果展示&#xff1a; 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&q…

BUUCTF---easyre1

1.记录一下第一次做逆向题目 2.题目描述&#xff0c;下载附件 3.解压之后是一个可执行文件&#xff0c;先用PE查看是否有壳 4.没有壳&#xff0c;接下来用ida打开&#xff0c;直接拖进ida即可&#xff0c;接下来使用快捷键fnshiftf12查看字符&#xff0c;若是没有出现搜索框&a…

探究精酿啤酒的秘密:原料中的天然酵母与纯净水质

在啤酒的世界中&#xff0c;Fendi Club精酿啤酒以其与众不同的口感和深远的余味吸引了全球的啤酒爱好者。而这一切&#xff0c;都归功于其选用的上好原料&#xff0c;特别是天然酵母和纯净水质。 天然酵母是啤酒的灵魂。与工业生产的啤酒酵母不同&#xff0c;天然酵母富含丰富的…

navicat过期了,直接用idea连接mysql

1、我的是社区版&#xff0c;需要下载一个插件&#xff0c;直接搜索安装即可。 2、找到data source&#xff0c;点击mysql 3、你们熟悉的&#xff0c;输入账户密码&#xff0c;点击test Connection测试是否连接成功 4、这个本来是在右边&#xff0c;但是你可以把他挪到左边。 5…

Neo4j 批量导入数据 从官方文档学习LOAD CSV 命令 小白可食用版

学习LOAD CSV&#x1f680; 在使用Neo4j进行大量数据导入的时候&#xff0c;发现如果用代码自动一行一行的导入效率过低&#xff0c;因此明白了为什么需要用到批量导入功能&#xff0c;在Neo4j中允许批量导入CSV文件格式&#xff0c;刚开始从网上的中看了各种半残的博客或者视频…

Servlet容器部署教程

Servlet容器介绍 Sercvlet是基于java的动态网站开发技术&#xff0c;其所有类和组件都是基于java实现的&#xff0c;要想使用Servlet&#xff0c;就必须提前配置好java运行环境。Servlet基于java&#xff0c;可以使用几乎全部的java API&#xff0c;所以它的功能异常强大&…

Oracle 层级查询(Hierarchical Queries)

如果一张表中的数据存在分级&#xff08;即数据间存在父子关系&#xff09;&#xff0c;利用普通SQL语句显示数据间的层级关系非常复杂&#xff0c;可能需要多次连接才能完整的展示出完成的层级关系&#xff0c;更困难的是你可能不知道数据到底有多少层。而利用Oracle的层级查询…

Android Framework 通过脚本动态修改应用私有文件执行权限

你只活一次 要悦己 脚本配置 Android_source/device/sprd/***/test/test_chmod.rc service test_chmod /vendor/bin/test_chmod.shuser rootdisabledoneshoton property:sys.test_chmodtruestart test_chmodAndroid_source/device/sprd/***/test/test_chmod.sh #!/system/bin/…

【CRC】一文搞懂CRC-8 SAE J1850 ZERO校验和

CRC在线计算 一、什么是 CRC 校验和 CRC —— Cyclic redundancy check 循环冗余校验&#xff0c;一种校验接收到的数据是否完整的算法&#xff0c;广泛应用于数据通信&#xff0c;大概流程如下 二、CRC-8 如何计算 首先&#xff0c;想要确定一个 CRC 算法&#xff0c;我们需…

论文笔记:Evaluating the Performance of Large Language Models on GAOKAO Benchmark

1 论文思路 采用zero-shot prompting的方式&#xff0c;将试题转化为ChatGPT的输入 对于数学题&#xff0c;将公式转化为latex输入 主观题由专业教师打分 2 数据 2010~2022年&#xff0c;一共13年间的全国A卷和全国B卷 3 结论 3.1 不同模型的zeroshot 高考总分 3.2 各科主…

web自动化测试框架都是有哪些?

Web自动化测试框架主要有以下几种&#xff1a; 1.Selenium&#xff1a;轻量级的Web自动化测试框架&#xff0c;支持多种Web浏览器和语言的集成。Selenium提供了一个IDE来录制和运行自动化测试脚本&#xff0c;还提供了WebDriver&#xff0c;可以通过编程语言编写自动化测试脚本…

知识积累(四):无

文章目录 1. KL散度2. GELU 激活函数3. 向量运算4. bert4.1 词嵌入4.2 cross-encoder 模型4.3 bert 架构4.4 bert 池化操作 5. Fid 模型&#xff08;Fusion-in-Decoder&#xff09;6. 多分类损失函数6.1 交叉熵损失6.2 softmax 损失 7. t-sne8. NDCG参考资料 1. KL散度 衡量两…

Skia最新版CMake编译

运行示例&#xff1a;example/HelloWorld.cpp Skia: 2024年03月08日 master分支: 993a88a663c817fce23d47394b574e19d9991f2f 使用CMake编译 python tools/git-sync-depsbin/gn gen out/config --idejson --json-ide-script../../gn/gn_to_cmake.py此时output目录会生成CM…

动态规划刷题总结(入门)

目录 什么是动态规划算法 如何判断题目中将使用动态规划算法&#xff1f; 动态规划题目做题步骤 动态规划题目解析 泰波那契数模型 第 N 个泰波那契数 三步问题 使用最小花费爬楼梯 路径问题 不同路径 不同路径 Ⅱ 珠宝的最高价值 下降最短路径和 地下城游…

照片的动态效果怎么弄?分享一个方法快速制作

动态的照片能够吸引注意力&#xff0c;增强视觉效果让信息更加生动有趣。那么&#xff0c;想要让自己手里的照片也变成有动态效果的图片时要怎么操作呢&#xff1f;这时候&#xff0c;只需要使用动态图片制作&#xff08;https://www.gif.cn/&#xff09;工具-GIF中文网&#x…

基于SSM SpringBoot vue家教交流平台

基于SSM SpringBoot vue家教交流平台 系统功能 管理员登录 家长登录注册 学生登录注册 教师登录注册 个人中心 家长信息管理 学生信息管理 教师信息管理 招聘家教管理 应聘家教管理 确认招聘管理 论坛管理 系统管理 我的收藏管理 管理员管理 开发环境和技术 开发语言&#x…

畅享精酿啤酒与意式面包的简单美味

在忙碌的生活中&#xff0c;我们时常渴望寻找那份简单的美好。而Fendi Club啤酒与意式面包的搭配&#xff0c;正是这种美好体验的代表。它们以其简洁的味道和口感&#xff0c;成为了无数人心中的佳品。 Fendi Club啤酒&#xff0c;以其醇厚的口感和细腻的泡沫&#xff0c;成为了…

【MySQL系列 05】Schema 与数据类型优化

良好的数据库 schema 设计和合理的数据类型选择是 SQL 获得高性能的基石。 一、选择优化的数据类型 MySQL 支持的数据类型非常多&#xff0c;选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据&#xff0c;下面几个简单的原则都有助于做出更好的选择。 1. …