【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.模拟手工算法的时候没考虑到可以划分边界做递归,思路不清晰。

相关推荐
咱就是说不配啊1 小时前
3.20打卡day34
数据结构·c++·算法
小张会进步1 小时前
数组:二维数组
java·javascript·算法
佑白雪乐1 小时前
LCR 175. 计算二叉树的深度
算法·深度优先
阿Y加油吧1 小时前
力扣打卡day07——最大子数组和、合并区间
算法
圣保罗的大教堂1 小时前
leetcode 3567. 子矩阵的最小绝对差 中等
leetcode
2401_831824961 小时前
嵌入式C++驱动开发
开发语言·c++·算法
靠沿1 小时前
【优选算法】专题十八——BFS解决拓扑排序问题
算法·宽度优先
cui_ruicheng2 小时前
C++数据结构进阶:哈希表实现
数据结构·c++·算法·哈希算法·散列表
前端摸鱼匠2 小时前
面试题4:多头注意力(MHA)相比单头注意力的优势是什么?Head数如何影响模型?
人工智能·ai·面试·职场和发展·求职招聘