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

news2024/11/27 16:24:29

面试经典算法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    7

preorder:  [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 = val
        self.left = left
        self.right = right

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder:
            return None
        
        root = 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.coloradmin.cn/o/1587300.html

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

相关文章

02 - Git 之命令 +

1 Git相关概念 1.1 以下所谈三个区&#xff0c;文件并不只是简单地在三个区转移&#xff0c;而是以复制副本的方式转移 使用 Git 管理的项目&#xff0c;拥有三个区域&#xff0c;分别是 Working area工作区&#xff08;亦称为 工作树Working Tree&#xff09;、stage area …

学习JavaEE的日子 Day33 File类,IO流

Day33 1.File类 File是文件和目录路径名的抽象表示 File类的对象可以表示文件&#xff1a;C:\Users\Desktop\hhy.txt File类的对象可以表示目录路径名&#xff1a;C:\Users\Desktop File只关注文件本身的信息&#xff08;文件名、是否可读、是否可写…&#xff09;&#xff0c…

简单了解JVM

一.JVM简介 jvm及Java virtual machineJava虚拟机&#xff0c;它是一个虚构出来的计算机&#xff0c;一种规范。其实抛开这么专业的句子不说&#xff0c;就知道 JVM 其实就类似于一台小电脑运行在 windows 或者 linux 这些操作系统环境下即可。它直接和操作系统进行交互&#…

无人新零售引领的创新浪潮

无人新零售引领的创新浪潮 在数字化时代加速演进的背景下&#xff0c;无人新零售作为商业领域的一股新兴力量&#xff0c;正以其独特的高效性和便捷性重塑着传统的购物模式&#xff0c;开辟了一条充满创新潜力的发展道路。 依托人脸识别、物联网等尖端技术&#xff0c;无人新…

中位数和众数-第12届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第49讲。 中位数和众数&…

初识 QT

初始QT 什么是QTQT发展史QT支持的平台QT的优点QT的应用场景搭建QT开发环境QT的开发工具概述QT下载安装 使用QT创建项目QT 实现Hello World程序使用按钮控件来实现使用标签控件来实现 项目文件解析widget.hmain.cppwidget.cppwidget.ui.pro文件 对象树QT 窗口坐标体系 什么是QT …

《战神4》和《战神5》有什么联系吗 苹果电脑如何运行《战神4》苹果电脑玩战神 Mac玩游戏 战神5攻略 crossover激活码

《战神4》&#xff08;God of War 2018&#xff09;和《战神5》&#xff08;God of War: Ragnark&#xff09;是一对引人注目的游戏作品&#xff0c;它们不仅在游戏界引起了广泛的关注&#xff0c;也给玩家带来了深入探索北欧神话世界的机会。这两部游戏之间的联系不仅体现在剧…

hbase基础shell用法

HBase中用create命令创建表&#xff0c;具体如下&#xff1a; create student,Sname,Ssex,Sage,Sdept,course 此时&#xff0c;即创建了一个“student”表&#xff0c;属性有&#xff1a;Sname,Ssex,Sage,Sdept,course。因为HBase的表中会有一个系统默认的属性作为行键&#x…

移远通信:立足5G RedCap新质生产力,全力推动智能电网创新发展

随着全球能源结构的转型和电力需求的持续增长&#xff0c;智能电网产业迎来了新的发展机遇。而物联网、大数据等前沿技术的创新和应用&#xff0c;正在为电力行业的发展注入强劲的新质生产力。 4月9日&#xff0c;第四十八届中国电工仪器仪表产业发展技术研讨及展会在杭州拉开帷…

debian安装和基本使用

debian安装和基本使用 文章目录 debian安装和基本使用1. 为什么选择debian2. 如何下载Debian2.1 小型安装镜像2.2 完整安装镜像 3. Debian操作系统安装3.1 创建Debian虚拟机3.2 安装操作系统 4. Debian系统的初始设置4.1 桌面环境的配置4.2 配置网络4.3 生效网络配置4.4 配置de…

【ubuntu20.04】安装GeographicLib

下载地址 GeographicLib: Installing GeographicLib 我们是ubuntu20.04 &#xff0c;所以下载第一个 GeographicLib-2.3.tar.gz 接着跟着官方步骤安装&#xff0c;会出错&#xff01;&#xff01;&#xff01;&#xff01;马的 官方错误示例&#xff1a;tar xfpz Geographi…

React + three.js 3D模型骨骼绑定

系列文章目录 React 使用 three.js 加载 gltf 3D模型 | three.js 入门React three.js 3D模型骨骼绑定React three.js 3D模型面部表情控制 项目代码(github)&#xff1a;https://github.com/couchette/simple-react-three-skeleton-demo 项目代码(gitcode)&#xff1a;https:…

solidworks electrical 2D和3D有什么区别

SolidWorks Electrical 是一款专为电气设计开发的软件工具&#xff0c;它提供了两种主要的工作环境&#xff1a;2D电气设计和3D电气集成设计。两者在功能和应用场景上存在显著的区别&#xff1a; SolidWorks Electrical 2D 设计 特点与用途&#xff1a; SolidWorks Electrica…

贪心算法:柠檬水找零

题目链接&#xff1a;860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 收的钱只能是5、10、20美元&#xff0c;分类讨论&#xff1a;收5美元无需找零&#xff1b;收10美元找零5元&#xff1b;收20美元找零15美元。其中对于找零15美元的方案有两种&#xff0c;此处涉及…

集群开发学习(一)(安装GO和MySQL,K8S基础概念)

完成gin小任务 参考文档&#xff1a; https://www.kancloud.cn/jiajunxi/ginweb100/1801414 https://github.com/hanjialeOK/going 最终代码地址&#xff1a;https://github.com/qinliangql/gin_mini_test.git 学习 1.安装go wget https://dl.google.com/go/go1.20.2.linu…

华为OD技术面试-有序数组第K最小值

背景 2024-03-15华为od 二面&#xff0c;记录结题过程 有序矩阵中第 K 小的元素 - 力扣&#xff08;LeetCode&#xff09; https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/submissions/512483717/ 题目 给你一个 n x n 矩阵 matrix &#xff0c;其…

Ubuntu 安装Java、Git、maven、Jenkins等持续集成环境

Ubuntu 持续集成 安装OpenJdk 查看所有可安装的 JDK 版本 apt list OpenJDK\*使用 apt 安装 JDK&#xff08;以 11为例&#xff09;,最好是用11&#xff0c;java8对应的jenkins会有兼容问题。 sudo apt install openjdk-11-jdk openjdk-11-jre安装成功后&#xff0c;可以使用以…

图像处理与视觉感知---期末复习重点(7)

文章目录 一、图像压缩1.1 三种冗余1.2 模型1.3 信息测量 二、无误差压缩2.1 哈夫曼编码2.1.1 步骤2.1.2 例题 2.2 算术编码 三、变换编码 一、图像压缩 1.1 三种冗余 1. 三种基本的是数据冗余为&#xff1a;编码冗余、像素间冗余、心理视觉冗余。 2. 编码冗余&#xff1a;如果…

unity——Button组件单击双击长按功能

1.实现单击、双击、长按功能 using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; public class ButtonControl_Click_Press_Double : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler {publi…

反序列化漏洞笔记

1 PHP 序列化基础概念 1.1 什么是序列化 序列化可以实现将对象压缩并格式化&#xff0c;方便数据的传输和存储。 为什么要序列化&#xff1f; PHP 文件在执行结束时会把对象销毁&#xff0c;如果下次要引用这个对象的话就很麻烦&#xff0c;所以就有了对象序列化&#xff0…