算法4.前序中序重建二叉树


题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

示例 1:

给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:
    3
   / \
  9  20
    /  \
   15   7

解题思路

方法一:使用栈解决

时间复杂度:$O(N)$ 前序遍历和后序遍历都被遍历。

空间复杂度:$O(N)$ 额外使用栈存储已经遍历过的节点。

代码如下

class Solution {
public:
    map<int,int> map_inorder;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 前序:[3,9,20,15,7]
        // 中序:[9,3,15,20,7]
        if(inorder.size() == 0)
            return 0;

        for(int i=0; i<inorder.size(); i++){
            map_inorder[inorder[i]] = i;
        }

        TreeNode* root = new TreeNode();
        int left = 0;
        int part_root = 0;
        int right = inorder.size()-1;
        root = recure(part_root,left,right,preorder,inorder);
        // cout<<root->val<<endl;
        return root;
    }
    TreeNode* recure(int part_root,int left,int right,vector<int>& preorder,vector<int>& inorder){
        if(left>right) return 0;
        int mid = map_inorder[preorder[part_root]];
        // cout<<mid<<endl;
        TreeNode* root = new TreeNode();
        root->val = preorder[part_root];
        root->left = recure(part_root+1,left,mid-1,preorder,inorder);
        root->right = recure(part_root+1+mid-left,mid+1,right,preorder,inorder);
        return root;

    }
};

解法分析

如果使用栈来解决首先要搞懂一个知识点,就是前序遍历挨着的两个值比如mn,他们会有下面两种情况之一的关系。

  1. nm左子树节点的值。
  2. nm右子树节点的值或者是m某个祖先节点的右节点的值。

解法注意点

  • 根据中序队列首元素,遍历前序队列,不相等则入栈,并一直找到左子树。
  • 若相等,则开始出栈,前进中序队列index,相等则不断出栈。
  • 当有不相等,则说明该节点是父节点,将前序队列下一个节点放到这个节点的右子树上
方法二:递归

时间复杂度:$O(N)$

空间复杂度:$O(N)$

代码如下

class Solution {
public:
    map<int,int> map_inorder;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 前序:[3,9,20,15,7]
        // 中序:[9,3,15,20,7]
        if(inorder.size() == 0)
            return 0;

        for(int i=0; i<inorder.size(); i++){
            map_inorder[inorder[i]] = i;
        }

        TreeNode* root = new TreeNode();
        int left = 0;
        int part_root = 0;
        int right = inorder.size()-1;
        root = recure(part_root,left,right,preorder,inorder);
        // cout<<root->val<<endl;
        return root;
    }
    TreeNode* recure(int part_root,int left,int right,vector<int>& preorder,vector<int>& inorder){
        if(left>right) return 0;
        int mid = map_inorder[preorder[part_root]];
        // cout<<mid<<endl;
        TreeNode* root = new TreeNode();
        root->val = preorder[part_root];
        root->left = recure(part_root+1,left,mid-1,preorder,inorder);
        root->right = recure(part_root+1+mid-left,mid+1,right,preorder,inorder);
        return root;

    }
};

解法分析

  • 递推参数: 前序遍历中根节点的索引pre_root、中序遍历左边界in_left、中序遍历右边界in_right
  • 终止条件: 当 in_left > in_right ,子树中序遍历为空,说明已经越过叶子节点,此时返回 $null$。
  • 递推工作:
    • 建立根节点root: 值为前序遍历中索引为pre_root的节点值。
    • 搜索根节点root在中序遍历的索引i: 为了提升搜索效率,本题解使用哈希表 dic 预存储中序遍历的值与索引的映射关系,每次搜索的时间复杂度为 $O(1)$。
    • 构建根节点root的左子树和右子树: 通过调用 recur() 方法开启下一层递归。
      左子树: 根节点索引为 pre_root + 1 ,中序遍历的左右边界分别为 in_lefti - 1
      右子树: 根节点索引为 i - in_left + pre_root + 1(即:根节点索引 + 左子树长度 + 1),中序遍历的左右边界分别为 i + 1in_right
  • 返回值: 返回 root,含义是当前递归层级建立的根节点 root 为上一递归层级的根节点的左或右子节点。

解法注意点

  • 根据题目描述输入的前序遍历和中序遍历的结果中都不含重复的数字,其表明树中每个节点值都是唯一的。

前序遍历特点: 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序,
中序遍历特点: 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序,
后序遍历特点: 节点按照 [ 左子树 | 右子树 | 根节点 ] 排序,


文章作者: ray
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ray !
评论
 上一篇
算法5.旋转数组的最小数字 算法5.旋转数组的最小数字
题目描述把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例
2020-09-25
下一篇 
算法3.链表反转 算法3.链表反转
题目描述输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 示例 1: 输入:head = [1,3,2] 输出:[2,3,1] 解题思路方法一:链表拼接 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 代码如下
2020-09-20
  目录