题目:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
思路:
前序遍历:根左右(例如:【1, 2, 4,7, 3, 5, 6, 8】)
中序遍历:左根右(对应:【4, 7, 2, 1, 5, 3, 8, 6】)
根据这个条件,我们先分析不使用代码的时候我们如何还原一棵树
①找到前序遍历中当前第一个元素,将其作为当前当前这棵树的root结点
【1】
②在中序遍历中找到这个节点,并将中序遍历分成两部分,左侧就是这个root结点的左子树内容,右侧就是这个root结点的右子树内容;(中序遍历:【4, 7, 2, 1, 5, 3, 8, 6】)
左侧:【4, 7, 2】; root:【1】;右侧:【5, 3, 8, 6】;
③对左子树和右子树重复① 和 ②的过程;
很显然这是一个递归调用的过程;
其中的关键:1. 递归结束的标志是什么? 2. 在递归调用过程中,入参需要传入子树的前序遍历和中序遍历/ 或是索引,这里如何保证索引正确
首先:如果我们使用索引作为标志,那么递归结束的标志应该是当前传入的子树的开始索引与结束索引出现交叉,我们在设计递归传入参数的时候,应该使得索引始终是合法的,这样我们就只需要检测是否出现交叉就ok了;
第二:我们应该计算出中序遍历中我们选定的root结点左右两侧剩余元素的个数,这样在前序遍历的数组中我们才可以找到合适个数的元素分别传入递归调用的左子树函数和右子树函数;
Code:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* constructTree(vector<int>& preorder,int p_start,int p_end ,vector<int>& inorder,int i_start,int i_end)
{
if(p_start <= p_end && i_start <= i_end && p_start >=0 && i_start >= 0){
TreeNode* root = new TreeNode(preorder[p_start]);
int i_left_remian = 0, i_right_remian = 0;
int i_root = 0;
for(int i = i_start ; i <= i_end; i++)
{
if(inorder[i] == root->val){
i_root = i;
}
}
i_left_remian = i_root - i_start; // 根节点左侧剩下的元素个数(包含i_start)
i_right_remian = i_end-i_start-1; // 根节点右边剩下元素个数
root->left = constructTree(preorder,p_start+1, p_start+i_left_remian , inorder, i_start, i_root-1);
root->right = constructTree(preorder,p_start+i_left_remian + 1, p_end , inorder, i_root+1, i_end);
return root;
}
else
return NULL;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size() == 0 || (inorder.size() != preorder.size()))
return NULL;
int p_start = 0, p_end = preorder.size() -1;
int i_start = 0 ,i_end = inorder.size() -1;
TreeNode* root = constructTree(preorder,p_start, p_end , inorder, i_start, i_end);
return root;
}
};