力扣第105题 通过前中序列 构造二叉树 c++ 数据结构必学

题目

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

中等

相关标签

 数组 哈希表 分治 二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

思路和解题方法

        首先,定义了一个私有函数traversal,用于递归构建二叉树。函数的参数包括前序遍历结果数组、中序遍历结果数组以及它们的起始和终止位置。

在函数内部,首先判断递归终止条件,即当前前序遍历区间只有一个元素时,直接创建节点并返回。

        然后,通过前序遍历结果数组的第一个元素确定根节点的值,并创建根节点。

接下来,通过在中序遍历结果数组中查找根节点的值,确定中序遍历结果数组的切割点(即根节点的位置)。

将中序遍历结果数组切割成左右两个区间,分别对应左子树和右子树。

同时,根据中序遍历结果数组的切割点计算出前序遍历结果数组的左右两个区间。

        然后,递归调用traversal函数分别构建左子树和右子树,并将其返回的结果分别赋值给根节点的左子节点和右子节点。

        最后,在主函数buildTree中判断输入的前序遍历和中序遍历数组是否为空,若为空则直接返回空指针。

        调用traversal函数,并传入前序遍历和中序遍历数组的起始和终止位置,得到构建好的二叉树,最后返回根节点。

复杂度

        时间复杂度:

                O(n)

时间复杂度为O(n),其中n是树中节点的数量。这是因为对于每个节点,需要遍历中序数组和前序数组来找到其在数组中的位置,而数组的总长度为n。

        空间复杂度

                O(n)

空间复杂度为O(n),其中n是树中节点的数量。这是因为在递归过程中,需要创建节点对象以及存储数组的切割区间,切割区间的总长度为n。

c++ 代码

class Solution { // 定义一个类名为Solution
private: // 定义类的私有部分
        TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) { // 定义一个私有成员函数traversal,接收两个vector(前序遍历和中序遍历)和相应的开始和结束索引
        if (preorderBegin == preorderEnd) return NULL; // 如果前序遍历的开始和结束索引相同,说明当前子树为空,返回NULL

        int rootValue = preorder[preorderBegin]; // 取出前序遍历中开始位置的元素作为当前子树的根节点值
        TreeNode* root = new TreeNode(rootValue); // 创建一个新的TreeNode对象,其值就是rootValue

        if (preorderEnd - preorderBegin == 1) return root; // 如果前序遍历的开始和结束索引之差为1,说明当前子树只有一个节点,直接返回该节点

        int delimiterIndex; // 定义一个变量用于保存分割点在中序遍历中的索引
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) { // 从中序遍历的开始位置遍历到结束位置
            if (inorder[delimiterIndex] == rootValue) break; // 如果找到与根节点值相同的元素,结束循环
        }
        // 在中序遍历中找到根节点对应的分割点,将中序遍历分为左右两部分
        // 中序左区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin; // 左区间的开始位置设为开始位置
        int leftInorderEnd = delimiterIndex; // 左区间的结束位置设为分割点位置
        // 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = delimiterIndex + 1; // 右区间的开始位置设为分割点位置+1
        int rightInorderEnd = inorderEnd; // 右区间的结束位置设为结束位置

        // 在前序遍历中按照中序的分割点也进行分割
        // 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
        int leftPreorderBegin =  preorderBegin + 1; // 前序左区间的开始位置设为前序开始位置+1(因为根节点已经取走了)
        int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin; // 前序左区间的结束位置为前序开始位置+1+中序左区间大小(左闭右开)
        // 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
        int rightPreorderBegin = preorderBegin + 1 + delimiterIndex - inorderBegin; // 前序右区间的开始位置为前序开始位置+1+中序左区间大小
        int rightPreorderEnd = preorderEnd; // 前序右区间的结束位置为前序的结束位置

        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  preorder, leftPreorderBegin, leftPreorderEnd); // 递归处理左子树
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd); // 递归处理右子树

        return root; // 返回处理完成的根节点
    }

public: // 定义类的公有部分
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { // 定义公有函数buildTree,接收两个vector(前序遍历和中序遍历)作为参数
        if (inorder.size() == 0 || preorder.size() == 0) return NULL; // 如果中序遍历或前序遍历为空,则返回NULL

        // 参数坚持左闭右开的原则
        return traversal(inorder, 0, inorder.size(), preorder, 0, preorder.size()); // 调用私有函数traversal处理数据并返回处理完成的根节点
    }
};

觉得有用的话可以点点赞,支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每天都会不定时更新哦  >人<  。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值