中等
相关标签
相关企业
给定两个整数数组
preorder和inorder,其中preorder是二叉树的先序遍历,inorder是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1:
1
2 输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]示例 2:
1
2 输入: preorder = [-1], inorder = [-1]
输出: [-1]提示:
1 <= preorder.length <= 3000inorder.length == preorder.length-3000 <= preorder[i], inorder[i] <= 3000preorder和inorder均 无重复 元素inorder均出现在preorderpreorder保证 为二叉树的前序遍历序列inorder保证 为二叉树的中序遍历序列
1 | class Solution { |
核心思路(一句话)
前序遍历的第一个元素是当前子树的根节点;在中序遍历中找到这个根节点后,根左侧是左子树、右侧是右子树。把左右子树在先序/中序里的区间算清楚后,递归构造即可。
代码主要部分与含义
1 | private: |
index:把中序遍历值 -> 下标 的哈希表,作用是 O(1) 定位某个值在中序序列中的位置(避免每次线性查找)。
1 | TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, |
myBuildTree:递归函数,负责构造区间[preorder_left, preorder_right](先序)和[inorder_left, inorder_right](中序)所描述的子树,并返回该子树的根指针。- 这两个区间一一对应,代表 同一棵子树 在先序与中序中的范围。
1 | if (preorder_left > preorder_right) { |
- 终止条件:如果先序区间为空(左界大于右界),说明没有节点,返回
nullptr。
1 | int preorder_root = preorder_left; |
preorder_root:先序区间的第一个位置就是当前子树的根在先序中的下标。inorder_root:通过哈希表得到根在中序数组中的下标。
1 | TreeNode* root = new TreeNode(preorder[preorder_root]); |
- 新建根节点,值为
preorder[preorder_root]。
1 | int size_left_subtree = inorder_root - inorder_left; |
- 关键:左子树节点数等于中序区间中从
inorder_left到inorder_root-1的元素个数,所以size_left_subtree = inorder_root - inorder_left。
1 | root->left = myBuildTree(preorder, inorder, |
- 构造左子树:
- 先序中,根节点后面
size_left_subtree个节点属于左子树,所以先序左子树区间是[preorder_left+1, preorder_left+size_left_subtree]。 - 中序左子树区间是
[inorder_left, inorder_root-1]。
- 先序中,根节点后面
1 | root->right = myBuildTree(preorder, inorder, |
- 构造右子树:
- 先序右子树区间从
preorder_left + size_left_subtree + 1开始到preorder_right结束(跳过根和左子树的元素)。 - 中序右子树区间是
[inorder_root+1, inorder_right]。
- 先序右子树区间从
最后 return root; 把建好的子树返回。
1 | TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { |
- 入口函数:先建哈希表
index(中序值到下标),然后对整棵树的区间[0,n-1]调用递归构造。
具体例子(手把手走一次)
设
preorder = [3, 9, 20, 15, 7]inorder = [9, 3, 15, 20, 7]
入口:myBuildTree(..., preorder_left=0, preorder_right=4, inorder_left=0, inorder_right=4)
- 根是
preorder[0] = 3。在中序中3的下标inorder_root = 1(因为inorder = [9,3,15,20,7])。 - 左子树节点数
size_left_subtree = inorder_root - inorder_left = 1 - 0 = 1。 - 左子树区间:
- 先序:
[preorder_left+1, preorder_left+size_left_subtree] = [1,1](元素[9]) - 中序:
[inorder_left, inorder_root-1] = [0,0](元素[9])
递归会创建节点9,它没有子节点(因为左右区间会变为空)。
- 先序:
- 右子树区间:
- 先序:
[preorder_left + size_left_subtree + 1, preorder_right] = [2,4](元素[20,15,7]) - 中序:
[inorder_root+1, inorder_right] = [2,4](元素[15,20,7])
递归下来会把20作为根,15为其左子、7为其右子 —— 最终得到与 LeetCode 示例相同的树。
- 先序:
这个例子显示了“先序第一个是根;用中序定位根;确定左子树节点数;在先序中切出对应的左/右区间” 的完整流程。
为什么用哈希表 index
每次需要在中序数组中查找根的位置,如果用线性查找,最坏情况下每次查找是 O(n),递归中会有 O(n) 次查找导致 O(n^2) 时间。用哈希表把查找降到 O(1),整体时间复杂度变成 O(n)。
复杂度分析
- 时间复杂度:O(n)。每个节点被创建一次;构造哈希表 O(n);递归处理每个节点常数工作。
- 空间复杂度:O(n)。哈希表需要 O(n),再加上递归栈最坏 O(n)(链状树时)。
注意事项与边界情况
- 元素必须互不相同:代码中将
inorder的值映射到下标,若有重复值,哈希表中会覆盖且定位不唯一,算法会出错。题目通常保证节点值唯一。 - 空树:若
preorder和inorder都为空,n=0,myBuildTree的第一次调用会立刻返回nullptr。 - 参数越界/不一致:调用前假设
preorder.size() == inorder.size(),且确实互为同一棵树的遍历序列。若输入不合法(长度不等或不匹配),行为未定义。 - 递归深度:极端情况下(退化为链表),递归深度为 n,可能导致栈溢出。可以改写为非递归或增加栈大小,视环境而定。
小结(要点回顾)
- 先序第一个是根;在中序中定位根,划分左右子树区间。
size_left_subtree = inorder_root - inorder_left是把“中序的划分”转成“先序的区间长度”的桥梁。- 用哈希表把中序定位从 O(n) 降到 O(1),从而总体 O(n)。
- 要求值唯一,递归区间要严格对应。
