云计算百科
云计算领域专业知识百科平台

【二叉树】LC 105.从前序与中序遍历序列构造二叉树

文章目录

  • 前言
  • 一、题目
    • 1、原题链接
    • 2、题目描述
  • 二、个人思路整理
    • 1、思路分析
    • 2、解题代码
  • 三、知识风暴

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

105.从前序与中序遍历序列构造二叉树

2、题目描述

在这里插入图片描述 在这里插入图片描述

二、个人思路整理

1、思路分析

核心是利用前序遍历和中序遍历各自的遍历顺序特点,通过递归分治来确定根节点及其左右子树的范围。 具体步骤:

  • 确定根节点:前序遍历(Preorder) 的顺序是 [根节点, 左子树, 右子树],因此当前区间的第一个元素 preorder[pre_left] 必然是当前子树的根节点。
  • 划分左右子树:
    • 中序遍历(Inorder) 的顺序是 [左子树, 根节点, 右子树]。
    • 在中序遍历中找到根节点所在的位置
      • in_root 后:in_root 左侧即为左子树的中序遍历序列,长度为 left_size = in_root – in_left。
      • in_root 右侧即为右子树的中序遍历序列。
  • 递归构建:
    • 知道了左子树的节点数量 left_size,就可以在前序遍历中切分出对应的区间:
      • 左子树的前序区间:[pre_left + 1, pre_left + left_size]
      • 左子树的中序区间:[in_left, in_root – 1]
      • 右子树的前序区间:[pre_left + left_size + 1, pre_right]
      • 右子树的中序区间:[in_root + 1, in_right]
    • 递归构建左、右子树并挂载到根节点上。
  • 哈希表加速:
    • 每次在中序遍历中线性查找根节点耗时

      O

      (

      n

      )

      O(n)

      O(n),会导致总时间复杂度退化为

      O

      (

      n

      2

      )

      O(n^2)

      O(n2)

    • 预先使用 unordered_map<int, int> 记录 inorder 中每个值的索引,可在

      O

      (

      1

      )

      O(1)

      O(1) 时间定位根节点。

  • 2、解题代码

    /**
    * Definition for a binary tree node.
    * struct TreeNode {
    * int val;
    * TreeNode *left;
    * TreeNode *right;
    * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    * };
    */

    class Solution {
    private:
    // 哈希表:存储中序遍历中 节点值->数组索引 的映射,以实现O(1)快速定位根节点
    unordered_map<int, int> in_pos;

    /**
    * @brief 递归构造二叉树
    * @param preorder 前序遍历序列引用
    * @param pre_left 当前子树在前序遍历序列中的起始下标
    * @param pre_right 当前子树在前序遍历序列中的结束下标
    * @param inorder 中序遍历序列引用
    * @param in_left 当前子树在中序遍历序列中的起始下标
    * @param in_right 当前子树在中序遍历序列中的结束下标
    * @return 当前子树的根节点指针
    */

    TreeNode* build(const vector<int>& preorder, int pre_left, int pre_right,
    const vector<int>& inorder, int in_left, int in_right) {
    // 递归终止条件:当前子树的区间为空
    if (pre_left > pre_right || in_left > in_right) {
    return nullptr;
    }

    // 1. 前序遍历的第一个节点为当前子树的根节点
    int root_val = preorder[pre_left];
    TreeNode* root = new TreeNode(root_val);

    // 2. 在中序遍历中找到根节点索引
    int in_root = in_pos[root_val];
    int left_size = in_root in_left; // 左子树节点数

    // 3. 递归构建左右子树
    root->left = build(preorder, pre_left + 1, pre_left + left_size, inorder, in_left, in_root 1);
    root->right = build(preorder, pre_left + left_size + 1, pre_right, inorder, in_root + 1, in_right);

    return root;
    }
    public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    int n = inorder.size();

    // 初始化中序遍历的索引映射
    for (int i = 0; i < n; i++) {
    in_pos[inorder[i]] = i;
    }

    // 从整棵树的全局区间开始递归构建
    return build(preorder, 0, n 1, inorder, 0, n 1);
    }
    };

    复杂度分析

    • 时间复杂度:

      O

      (

      n

      )

      O(n)

      O(n)。构建哈希表耗时

      O

      (

      n

      )

      O(n)

      O(n),递归访问每个节点一次,每次递归内部操作均为

      O

      (

      1

      )

      O(1)

      O(1)

    • 空间复杂度:

      O

      (

      n

      )

      O(n)

      O(n)。哈希表占用

      O

      (

      n

      )

      O(n)

      O(n) 空间,递归调用栈深度最坏情况下为

      O

      (

      n

      )

      O(n)

      O(n)(链状树),平衡状态下为

      O

      (

      log

      n

      )

      O(\\log n)

      O(logn)

    三、知识风暴

    前序遍历(Preorder Traversal) 是本题的核心:从前序与中序遍历序列构造二叉树,本质上就是利用前序遍历确定根节点、中序遍历划分左右子树,通过递归分治重建整棵树。前序遍历的顺序是 根 -> 左 -> 右,中序遍历的顺序是 左 -> 根 -> 右,两者结合即可唯一确定一棵二叉树。

    算法核心思想:

    • 前序遍历定根:前序遍历的第一个元素必然是当前子树的根节点,这是递归分治的起点。
    • 中序遍历划分:在中序遍历中找到根节点的位置后,其左侧即为左子树的中序序列,右侧即为右子树的中序序列,从而确定左右子树的范围。
    • 递归分治:根据中序划分出的左右子树节点数量,在前序遍历中切分出对应的左右子树区间,递归构建左右子树并挂载到根节点上。

    常见对比:递归 vs 迭代(显式栈)

    • 递归(分治):这是本题最直观的解法,利用递归调用栈天然保存了回溯路径,代码简洁清晰;但空间复杂度为

      O

      (

      h

      )

      O(h)

      O(h)

      h

      h

      h 为树高),最坏情况下(退化为链表)递归深度为

      O

      (

      n

      )

      O(n)

      O(n),存在栈溢出风险。

    • 迭代(显式栈):用栈模拟递归过程,每次从栈中取出一个节点并处理其左右子树区间,空间复杂度同样为

      O

      (

      n

      )

      O(n)

      O(n)(最坏情况),但避免了系统递归栈的开销,更可控、更安全。

    • 迭代(哈希表加速):这是本题的优化关键。预先使用 unordered_map 记录中序遍历中每个值的索引,可在

      O

      (

      1

      )

      O(1)

      O(1) 时间内定位根节点,避免每次线性查找导致总复杂度退化为

      O

      (

      n

      2

      )

      O(n^2)

      O(n2)

    哈希表加速思想:

    • 核心思想:中序遍历序列中每个节点的值都是唯一的,因此可以预先建立 值 -> 索引 的映射表,在递归过程中直接查表定位根节点,无需每次遍历查找。
    • 与本题的联系:本题的递归分治过程中,每次都需要在中序遍历中定位根节点。若不使用哈希表,每次定位耗时

      O

      (

      n

      )

      O(n)

      O(n),总复杂度退化为

      O

      (

      n

      2

      )

      O(n^2)

      O(n2);使用哈希表后,每次定位耗时

      O

      (

      1

      )

      O(1)

      O(1),总复杂度优化为

      O

      (

      n

      )

      O(n)

      O(n)

    • 注意事项:哈希表建立的前提是节点值唯一。若题目允许重复值,则需要额外的处理策略(如结合索引范围进一步约束)。

    使用要点:

    • 区间边界:递归时需同时维护前序和中序的左右边界(pre_left、pre_right、in_left、in_right),确保每次递归只处理当前子树的区间。
    • 左子树大小:left_size = in_root – in_left 是划分左右子树的关键,它决定了前序遍历中左右子树区间的切分位置。
    • 递归终止:当 pre_left > pre_right 或 in_left > in_right 时,说明当前子树为空,返回 nullptr 作为边界条件的兜底。
    • 空树处理:若输入序列为空,直接返回 nullptr,作为边界条件的兜底。

    算法变体与扩展:

    • 二叉树的中序遍历(LeetCode 94):理解中序遍历的序列特点是本题划分左右子树的基础。
    • 从中序与后序遍历序列构造二叉树(LeetCode 106):与本题对称,利用后序遍历确定根节点、中序遍历划分左右子树,可对比理解不同遍历组合的重建逻辑。
    • 将有序数组转换为二叉搜索树(LeetCode 108):同样是利用序列划分递归构建二叉树,可对比理解不同数据源下的构建策略。
    • 二叉树的层序遍历(LeetCode 102):与本题的深度优先构建不同,层序遍历采用广度优先,可对比理解树的两种遍历与构建方式。

    相关 LeetCode 例题:

    • 94. 二叉树的中序遍历(中序遍历序列特点)
    • 106. 从中序与后序遍历序列构造二叉树(对称的构建思路)
    • 108. 将有序数组转换为二叉搜索树(序列划分递归构建)
    • 102. 二叉树的层序遍历(广度优先遍历对比)
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【二叉树】LC 105.从前序与中序遍历序列构造二叉树
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!