leetcode题解:第105题Construct Binary Tree from Preorder and Inorder Traversal

本文介绍了如何使用递归和迭代两种方法解决LeetCode第105题,即根据给定的前序和中序遍历构建二叉树。解法一利用哈希表提高查找效率,递归构建树,时间复杂度和空间复杂度均为O(n)。解法二通过栈和指针跟踪中序遍历,同样达到O(n)的时间和空间复杂度。

https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

解法一、递归

前序遍历的序列组成形式为:根, {左子树前序结果}, {右子树前序结果}
中序遍历的序列组成形式为:{左子树中序结果}, 根, {右子树中序结果}

把上面这两行写出来,递归解法就很容易想到了。

很明显,我们需要知道左子树的节点个数,才好获取到左子树的前序结果。由于树中无重复元素,我们可以利用中序遍历的结果,获取根的下标,这样也就间接得到了左子树中序结果的数组长度,同时也达到了我们的目的。

如果在递归过程中每次都遍历中序结果去找根的下标,会耗费太多时间。注意到,中序结果是不会变的,所以完全可以利用一个哈希表来存储 值 → \rightarrow 下标 的映射。

代码
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        unordered_map<int, int> m;
        for (int i = 0; i < inorder.size(); ++i) m[inorder[i]] = i;
        return helper(m, preorder, 0, preorder.size() - 1, 0, preorder.size() - 1);
    }
private:
    TreeNode* helper(unordered_map<int, int>& m, vector<int>& preorder, int l1, int r1, int l2, int r2) {
        if (l1 > r1) return nullptr;
        TreeNode* root = new TreeNode(preorder[l1]);
        int index = m[preorder[l1]], left_num = index - l2;
        root->left = helper(m, preorder, l1 + 1, l1 + left_num, l2, index - 1);
        root->right = helper(m, preorder, l1 + left_num + 1, r1, index + 1, r2);
        return root;
    }
};
复杂度分析

前序结果和中序结果都需要遍历一次,时间 O ( n ) O(n) O(n),哈希表获取元素时间 O ( 1 ) O(1) O(1),所以总的时间复杂度为 O ( n ) O(n) O(n)。除了构造树所需的 O ( n ) O(n) O(n)空间外,还需要 O ( n ) O(n) O(n)的空间来存储哈希表, O ( h ) O(h) O(h)的空间来存储递归栈( h < n h < n h<n),所以总的空间复杂度 O ( n ) O(n) O(n)

解法二、迭代

迭代的方法比递归的要复杂很多。
首先,我们思考前序遍历中任意两个连续节点x和y的关系,只有两种情况:

  1. x有左儿子时,y是x的左儿子
  2. x没有左儿子时,y是x或者x的某个祖先的右儿子(并且x在这个祖先的左子树中)

我们用一个栈来维护当前节点的所有这样的祖先:当前节点在祖先的左子树中,栈顶节点就是当前节点。用一个index来指向中序遍历的序列。

初始时,根节点自然为preorder[0],我们从1开始遍历preorder。index = 0指向inorder[0]。根节点入栈。
栈顶节点为x,遍历到的值为y。

  • 如果x不等于inorder[index],这说明x是有左儿子的,导致中序遍历先打印了x的左儿子的值,否则中序遍历就会直接打印x的值,与前序的结果相同。
    此时y是x的左儿子
  • 如果x等于inorder[index],这说明x没有左儿子,如何判断y是哪个节点的右儿子呢?注意,当所有节点都只有左儿子时,前序遍历的结果与中序遍历的结果相反。这种情况下我们依次弹出栈顶的值,就会与inorder[index++]的值对应。如果在这个过程中又出现了值不相等的情况,那么就说明中序遍历输出了一个右儿子,此时最后弹出栈的那个节点就是我们要找的祖先。(从这个祖先到x的路径上的所有节点都没有右儿子)
    此时y是那个祖先的右儿子
  • 不论怎样,都要把y入栈(对于第二种情况,尽管y是其父亲的右儿子,但其父亲已经出栈了,保证了栈内节点的意义)
代码
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        stack<TreeNode*> s;
        int n = preorder.size();
        if (!n) return nullptr;
        int index1 = 0, index2 = 0;
        TreeNode* root = new TreeNode(preorder[index1++]);
        s.push(root);
        while (index1 < n) {
            TreeNode* node = s.top();
            TreeNode* tmp = new TreeNode(preorder[index1++]);
            if (node->val == inorder[index2]) {
                while (!s.empty() && s.top()->val == inorder[index2]) {
                    node = s.top();
                    s.pop();
                    index2++;
                }
                node->right = tmp;
            }
            else node->left = tmp;
            s.push(tmp);
        }
        return root;
    }
};
复杂度分析

时间复杂度和空间复杂度都是 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值