跳到主要内容

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

本节目标

用前序根节点和中序位置拆分半开区间,线性时间重建二叉树。

这道题是构造与二叉搜索树中“遍历关系确定结构”的代表。

查看 LeetCode 原题

题意与约束

节点值互不相同。前序遍历的第一个节点总是当前子树根;根在中序遍历中的位置将当前中序区间分成左右两部分,因此树可被唯一重建。

半开区间拆分

递归同时维护前序与中序的半开区间。若前序区间为空,当前子树为空。根值为 preorder[preLeft];借助中序位置表得到 rootIndex 后,左子树大小为 rootIndex - inLeft。据此可直接计算两棵子树的四个边界,不需要创建任何切片数组。

正确性依据

当前前序首元素是根。中序中根左侧恰好是左子树全部节点,数量确定后,前序中紧随根的同样数量节点也恰好属于左子树;余下节点属于右子树。递归对两个更小区间应用同一关系,空区间停止,因此重建树的两种遍历都与输入一致。

代码实现

C++17
#include <unordered_map>
#include <vector>

using namespace std;

class Solution {
private:
TreeNode* build(
const vector<int>& preorder,
int preLeft,
int preRight,
int inLeft,
int inRight,
const unordered_map<int, int>& inorderIndex
) {
if (preLeft >= preRight) {
return nullptr;
}

const int rootValue = preorder[preLeft];
const int rootIndex = inorderIndex.at(rootValue);
const int leftSize = rootIndex - inLeft;
TreeNode* root = new TreeNode(rootValue);
root->left = build(
preorder,
preLeft + 1,
preLeft + 1 + leftSize,
inLeft,
rootIndex,
inorderIndex
);
root->right = build(
preorder,
preLeft + 1 + leftSize,
preRight,
rootIndex + 1,
inRight,
inorderIndex
);
return root;
}

public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
unordered_map<int, int> inorderIndex;
for (int i = 0; i < static_cast<int>(inorder.size()); i++) {
inorderIndex[inorder[i]] = i;
}
return build(
preorder,
0,
static_cast<int>(preorder.size()),
0,
static_cast<int>(inorder.size()),
inorderIndex
);
}
};

复杂度分析

位置表建立与每个节点构造各进行一次,时间复杂度为 O(n)。位置表和递归栈分别使用 O(n)O(h) 额外空间。

易错点

  • 在线性扫描中序数组寻找根,最坏退化为 O(n²)
  • 左子树大小用错为 rootIndex,忘记减去当前中序左边界。
  • 对每层递归复制前序或中序子数组,增加不必要的空间与时间。

模式迁移

中序与后序构造的边界推导相同,只是后序末元素为根。遇到其他由序列恢复结构的问题,也应先找能唯一确定分割位置的信息。