题目
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
提示:
1.vin.length == pre.length;
2.pre 和 vin 均无重复元素;
3.vin出现的元素均出现在 pre里;
4.只需要返回根结点,系统会自动输出整颗树做答案对比;
数据范围:0≤n≤2000,节点的值 −10000≤val≤10000;
要求:空间复杂度 O(n),时间复杂度 O(n)
解析
本题需要抓住3个关键点:
1. 先序遍历的第一个值一定是整棵树的根节点;
2. 使用先序遍历时,遍历左子树的过程依然是先序遍历,中序遍历同理;
3. 中序遍历中,根节点所在位置左侧的值位于左子树,右侧的值位于右子树;
因此,可以通过先序第一个数找到根节点,通过遍历找到根节点在中序遍历中的位置iSite。
当我们确定了根节点在中序遍历中的位置后,开始考虑左右两子树的构建。
首先考虑左子树。左子树的根节点根据先序遍历“中左右”的顺序,应为目前先序遍历中,整棵树的根节点所在位置的后一位,即pre+1,在中序遍历中因为先遍历左子树,因此中序遍历序列的第1位就是左子树。
又因为在先序遍历中,一定会遍历完整棵左子树后再遍历右子树,又因为中序遍历iSite的左边都是左子树,因此构建左子树时,在先序遍历中的长度仅为iSite,在中序遍历中的长度也为iSite。
综上,每一次构建左子树时,都应当是:
tree->left = reConstructBinaryTree(pre + 1, iSite, vin, iSite);
再看右子树。右子树的根节点根据先序遍历“中左右”的顺序即为,根节点+左子树长度的后一位,pre+iSite+1,在中序遍历中,根据“左中右”的顺序,位于vin+iSite+1的位置。
而我们不知道左右子树是否有一样多的元素,因此不能盲目地认为右子树元素个数也为iSite。而是应当写为preLen-iSite-1,或vinLen-iSite-1。因preLen = vinLen,因此二者除了阅读外,值相等。
综上,每一次构建右子树时,都应当是:
tree->right = reConstructBinaryTree(pre + iSite + 1, preLen - iSite - 1, vin + iSite + 1, vinLen - iSite - 1);
二叉树的问题都可以归结为左右子树反复递归的过程,因此明确了根节点和左右子树的复建规则后,就可以写代码了。
代码
//pre是先序遍历,vin是中序遍历
struct TreeNode* reConstructBinaryTree(int* pre, int preLen, int* vin, int vinLen ) {
struct TreeNode* tree = 0; //新建一棵二叉树
int iSite = 0; //表示右子树的深度,遍历完成后代表根节点所在位置
if ((NULL == pre) || (0 == preLen))
return NULL; //若为空树,则返回NULL
tree = (struct TreeNode*)malloc(sizeof(struct TreeNode)); //调用一块空间来建立树
memset(tree, 0, sizeof(struct TreeNode)); //为新申请的内存做初始化工作
tree->val = pre[0]; //根节点一定是先序遍历的第一个值
while (pre[0] != vin[iSite])
{
iSite++;
} //在中序遍历中找到根节点所在的位置
//构建左子树,递归
tree->left = reConstructBinaryTree(pre + 1, iSite, vin, iSite);
//构建右子树,递归
tree->right = reConstructBinaryTree(pre + iSite + 1, preLen - iSite - 1, vin + iSite + 1, vinLen - iSite - 1);
return tree;
}