leetcode105/106、用前序(后序)和中序构造二叉树【优化后击败98.1%】

文章讲述了如何构建二叉树,首先介绍了未优化的版本,然后重点优化了查找根节点和切割序列的部分,通过哈希表和传递下标索引来减少时间和空间复杂度。

先说好,我不喜欢极致简洁的代码,不欣赏只追求简洁而忽略可读性的做法(我指的不是我超级详细的注释哈😹)

这里只给出了105题的代码,106也是差不多的。

构建思路 : (最好自己拿着例子跟着划一下)

因为前序的第一个位置的结点是根节点,通过前序找到根节点的值后,利用根节点的值去中序找到其所在下标来切割中序,再反过来拿中序切割好的左区间的size去切割前序。

经过对前序和中序的切割,我们就拿到了左前序和右前序区间,左中序和右中序区间,利用左前序和左中序、右前序和右中序递归处理左、右子树···重复直到构造完成

有了大致构建思路后,先搭建整体框架,再去想怎么实现以及细节方面

第一种、未优化的版本


    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() == 0) return nullptr;
        //1、用先序找到根节点的值
        int rootValue = preorder[0];
        TreeNode* root = new TreeNode(rootValue); 
        if (preorder.size() == 1) return root;
        //2、利用根的值在中序中找到根的位置
        int i = 0;
        while (inorder[i] != rootValue) i++;
        //3、利用根的位置i切割中序的左右区间 [0,i) i [i+1,end)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + i);
        vector<int> rightInorder(inorder.begin() + i + 1, inorder.end());
        //4、利用中序的左区间切割先序的左右区间 [1,i+1) [i+1,end)
        vector<int> leftPreorder(preorder.begin() + 1, preorder.begin() + i + 1);
        vector<int> rightPreorder(preorder.begin() + i + 1, preorder.end()); 
        //5、递归处理左区间和右区间
        root->left = buildTree(leftPreorder, leftInorder);//利用左先序和左中序
        root->right = buildTree(rightPreorder, rightInorder);//利用右先序和右中序
        return root;
    }
    

🧐优化:

优化一、

  1. 为什么需要该优化?

因为第二步每次都需要扫描中序遍历才能找到根节点,时间复杂度较高。而使用哈希表可以快速定位根节点,时间复杂度只需O(1)。

  1. 怎么样优化?

用哈希map进行哈希映射,key为节点的值,value为其在中序遍历中的出现位置,在构造二叉树之前遍历一遍中序遍历,就可以构造出哈希映射,往后构造的过程中就只需要O(1)的时间就能快速在中序中定位根节点

优化二、

  1. 为什么需要该优化?

因为每次递归(第三、四步)都需要生成中序和前序序列的一部分的拷贝,时间和空间复杂度都较高。

  1. 怎么样优化?

可以在递归函数参数中传入下标索引——通过下标索引直接在原数组上操作,用下标“切割”前序(中序)以表示区间,这样就不需要多次拷贝容器了

第二种、优化后的版本


    unordered_map<int, int> hash;
    // 防止晕乎先说明--要传四个参数:
    // preorder容器、先序区间的起始和结束下标、中序区间的起始下标
    // 其它的你会发现都用不到(~ ̄▽ ̄)~
    TreeNode* traversal(vector<int>& preorder, int preStart, int preEnd, int inStart) {
        //要传哪些参数呢?因为要减少容器的拷贝,所以用下标表示序列区间
        //因为要根据先序第一个位置得到根节点的值,因此需要传先序序列的引用和先序区间起始位置preStart,
        //且要根据中序左区间--[中序起始下标,i)的大小切割先序左右区间,因此要传中序的起始位置inStart,
        //又因为需要根据先序(或中序)区间的size判断是否结束递归(区间为空就结束),因此需要传先序的结束位置preEnd。
        //(这里先序区间我采用左闭右开[ )表示)

        if (preStart == preEnd) return nullptr;//当先序区间大小为0时

        //1、用先序找到根节点的值--位于先序序列第一个位置
        int rootValue = preorder[preStart];
        TreeNode* root = new TreeNode(rootValue); 
        //2、利用根的值在中序中找到根的位置

        int i =  hash[rootValue];//优化一、利用哈希映射,时间复杂度O(1)

        //3、利用根的位置i切割中序--[inStart, i) i [i+1, end)
        //4、利用中序的左区间大小len, 切割先序--preStart [preStart+1, preStart+1+len) [preStart+1+len, end)
        int len = i - inStart;//中序的左区间大小
        //5、递归处理左区间和右区间
        root->left = traversal(preorder, preStart + 1, preStart + 1 + len, inStart);//左先序和左中序
        root->right = traversal(preorder, preStart + 1 + len, preEnd, i + 1);//右先序和右中序
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //构造哈希映射,以在之后建树过程中快速定位根节点
        int i = 0;
        for(auto elem : inorder) {
            //因为是利用根的值在中序中找到根的位置,因此key是结点值而不是结点的下标
            hash[elem] = i++;
        }
        return traversal(preorder, 0, preorder.size(), 0);
    }
    

做完可以顺带把106、利用中序和后序构造二叉树654、最大二叉树做了,如果弄懂了这题的话这两题应该是能分分钟秒了
做了

如果对你有帮助的话那就太好啦🤗ヾ(≧▽≦*)o

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值