【leetcode】105. 从前序与中序遍历序列构造二叉树

文章目录


碎碎念

最近身体又变差了啊啊啊,这周一去健身就这样超负荷然后晕倒...于是又空了好几天去看医生和修养,然后又遇上生理期,所以这周的羽毛球估计也是去不了了,健身bb也还是说最好不要去,一定要我挨过生理期前三天再去。

还去查了糖耐,自己看了一眼结果好像可能也许不太妙...?不过也还没让医生看,到时候听听医生怎么说吧。反正也在健康饮食健康作息啦!太想健身太想打球了,希望快点好起来!


一、题目


二、思路和题解

1.思路

好难啊啊啊啊好难!!手工算法不难,但是代码好难写啊啊啊啊!

在讲手工算法之前,我们得知道前序遍历和中序遍历分别是怎么个顺序。前序遍历就是按照根左右 的顺序去遍历树的节点,这个顺序对于子树也是一样的;中序遍历则是按照左根右的顺序来遍历。

ok知道这个之后我们就可以用例1来体验一下手工算法啦~

例1的前序和中序遍历如下

preorder = 3,9,20,15,7

inorder = 9,3,15,20,7

因为前序遍历的顺序是根左右,所以前序遍历的第一个一定是根节点。这时候我们再来看中序遍历(左根右),就可以划分出左子树和右子树的部分了,也就是------

复制代码
inorder = [9 | 3 | 15,20,7]

其中9是左子树,3是根,15,20,7是右子树的部分。很显然的,左子树只有一个节点,这时候我们的树就长这样了:

plaintext 复制代码
        3      
       / \
      9   15,20,7 

那么右子树怎么处理呢?我们接着往下看。

还是先看前序遍历,这时候我们只看和右子树15,20,7有关的地方,也就是20,15,7。一样的,第一个是根节点,所以我们再去找中序遍历做划分:

复制代码
inorder = [15 | 20 | 7]

这时候我们继续处理右子树,就能得到完整的树啦。

plaintext 复制代码
        3 
       / \
      9   20 
         / \
        15  7 

以上是手工算法,那么代码怎么写呢?我们注意到做手工算法的时候我们一直在划分,且子树的部分也是重复的步骤,都是先去前序遍历序列找到根节点,再去中序遍历序列做划分。这时候就很自然的想到了用递归。我们每次找到根节点就创建节点并接到树里面,划分呢则利用下标(如preorder_leftpreorder_right)来做计算,终止条件就是当两个序列中只要有一个left>right,就说明没有节点了,这时候返回nullptr

然后是关于下标,我们分别给出preLeft,preRight,inLeft,inRight,preRoot,inRoot

很显然的,preRoot就是preLeft,而inRoot我们就需要去中序遍历序列里面找一下对应的下标,这一步我们可以暴力遍历,当然也可以用哈希表优化,下面的代码用的是哈希表。

当我们找到inRoot,中序遍历中左右子树的划分就出来了。左子树[inLeft , inRoot - 1],右子树[inRoot + 1 , inRight]。而前序遍历中,我们需要借助一些计算来获取两段的左右边界。我们先看左子树,左边界很显然是preLeft + 1,右边界这里表示的方法各有不同,我们既可以利用左子树节点个数守恒的关系来建立等式:
x − ( p r e L e f t + 1 ) = ( i n R o o t − 1 ) − i n L e f t x - (preLeft + 1)=(inRoot-1)-inLeft x−(preLeft+1)=(inRoot−1)−inLeft
x = i n R o o t − i n L e f t + p r e L e f t x=inRoot-inLeft+preLeft x=inRoot−inLeft+preLeft

也可以用一个变量来记录左子树那一段的长度,再加上preLeft(下面的代码用的是这个方法)。

右子树的左边界在上面基础上加1就可以了。

2.代码(递归)

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    unordered_map<int,int> val_to_index;
public:
    TreeNode* mybuildTree(vector<int>& preorder, int preorder_left, int preorder_right, vector<int>& inorder, int inorder_left, int inorder_right) {
        // 递归终止:没有节点了
        if (preorder_left > preorder_right || inorder_left > inorder_right) {
            return nullptr;
        }

        // 前序遍历第一个就是根节点,创建节点
        int root_val = preorder[preorder_left];
        TreeNode* root = new TreeNode(root_val);

        int preorder_root = preorder_left;
        int inorder_root = val_to_index[root_val];// 找根节点对应的中序遍历序列中的索引

        int left_tree_size = inorder_root - inorder_left;// 计算左子树数量,辅助后续拆分

        root->left = mybuildTree(preorder, preorder_left+1, preorder_left + left_tree_size, inorder, inorder_left, inorder_root-1);
        root->right = mybuildTree(preorder, preorder_left + left_tree_size + 1, preorder_right, inorder, inorder_root+1, inorder_right);

        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = inorder.size();
        for (int i=0;i<n;i++){
            val_to_index[inorder[i]] = i;
        }
        return mybuildTree(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
    }
};
  • 时间复杂度:O(n),其中 n 是树中的节点个数。
  • 空间复杂度:O(n),除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<n,所以总空间复杂度为 O(n)。

三、其他解法(迭代)

参考题解在这里。

这里直接贴代码了主播有点力竭了...(二刷的时候一定会补充的对)

cpp 复制代码
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]) {
                    node = stk.top();
                    stk.pop();
                    ++inorderIndex;
                }
                node->right = new TreeNode(preorderVal);
                stk.push(node->right);
            }
        }
        return root;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solutions/255811/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

四、错误回顾

1.模拟手工算法的时候没考虑到可以划分边界做递归,思路不清晰。

相关推荐
江畔柳前堤5 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore6 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考8 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX8 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面9 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号9 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽9 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI10 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者11 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者11 小时前
【征程6】校准量化中HistogramObserver解析
算法