题目描述
思路分析
挖掘前序中序遍历的特点
前序遍历的第一个节点是根节点,只要找到根节点在中序遍历中的位置,在根节点之前被访问的节点都位于左子树,在根节点之后被访问的节点都位于右子树,由此可知左子树和右子树分别有多少个节点。
由于树中的节点数量与遍历方式无关,通过中序遍历得知左子树和右子树的节点数量之后,可以根据节点数量得到前序遍历中的左子树和右子树的分界,因此可以进一步得到左子树和右子树各自的前序遍历和中序遍历,可以通过递归的方式,重建左子树和右子树,然后重建整个二叉树。
使用一个 Map 存储中序遍历的每个元素及其对应的下标,目的是为了快速获得一个元素在中序遍历中的位置。调用递归方法,对于前序遍历和中序遍历,下标范围都是从 0 到 n-1,其中 n 是二叉树节点个数。
递归方法的基准情形有两个:判断前序遍历的下标范围的开始和结束,若开始大于结束,则当前的二叉树中没有节点,返回空值 null。若开始等于结束,则当前的二叉树中恰好有一个节点,根据节点值创建该节点作为根节点并返回。
若开始小于结束,则当前的二叉树中有多个节点。在中序遍历中得到根节点的位置,从而得到左子树和右子树各自的下标范围和节点数量,知道节点数量后,在前序遍历中即可得到左子树和右子树各自的下标范围,然后递归重建左子树和右子树,并将左右子树的根节点分别作为当前根节点的左右子节点。
代码展示
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (!preorder.size()) {//条件判断
return nullptr;
}
TreeNode* root = new TreeNode(preorder[0]);//建立根节点
stack<TreeNode*> stk;//建立栈存储节点
stk.push(root);//入根节点
int inorderIndex = 0;//中序遍历的的元素下标
for (int i = 1; i < preorder.size(); ++i) {//遍历前序遍历数组
int preorderVal = preorder[i];//保存当前的元素值
TreeNode* node = stk.top();//获取栈顶元素
if (node->val != inorder[inorderIndex]) {//栈顶元素的值不等于当前中序遍历的元素
node->left = new TreeNode(preorderVal);//可以模拟前序遍历进行对左孩子的访问
stk.push(node->left);//入栈
}
else {//找到了最左节点
while (!stk.empty() && stk.top()->val == inorder[inorderIndex]) {
//相等val 说明是最右节点 需要弹栈并++index
node = stk.top();
stk.pop();//可pop是因为在push的时候已经进行过访问了
//每个节点在push的时候就对自身的val进行了存储但是需要结合前中进行right和left的访问
++inorderIndex;
}
node->right = new TreeNode(preorderVal);
stk.push(node->right);
}
}
return root;
}
};
结果分析
时间复杂度O(n)
空间复杂度O(n)