vlambda博客
学习文章列表

每日一道算法题04:重建二叉树

题目描述

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

例如,给出

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

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000

解题方案

前序遍历性质:节点按照 [根节点 | 左子树 | 右子树 ] 排序。

中序遍历性质:节点按照 [ 左子树 | 根节点 | 右子树 ] 排序。

以题目示例为例:

前序遍历划分 [3 | 9 | 20 15 7 ]

中序遍历划分 [ 9 | 3 | 15 20 7 ]

根据以上性质,可得出以下推论:

  • 前序遍历的首元素 为 树的根节点 node 的值。
  • 在中序遍历中搜索根节点 node 的索引 ,可将 中序遍历 划分为 [ 左子树 | 根节点 | 右子树 ]
  • 根据中序遍历中的左 / 右子树的节点数量,可将 前序遍历 划分为 [ 根节点 | 左子树 | 右子树 ]

如何找到第一个根节点?

为什么要是第一个根节点呢?因为你是从第一个开始根开始创建的呀。

前序遍历:根节点 -- 左节点 -- 右节点

中序遍历:左节点 -- 根节点 -- 右节点

第一个根节点必然出现在前序遍历的第一个位置!但是对于中序遍历来说,我们不知道第一个根前面左子树的长度,所以无法得知!因此最简单的切入点就是通过前序遍历找到第一个根节点

如何找到第一个左节点?

这个在前序遍历中也很好看出来,第一个左节点就在第一个根节点的右边1个单位,即:pre_root_index + 1

如何找到第一个右节点?

我们找到了第一个根,第一个左节点,那如何确定第一个右节点呢?

对于前序遍历而言,我们已经知道了根、左节点,那么很显然,右节点的下标 = 根节点下标 + 左子树的长度 + 1(在前序遍历数组中),现在唯一不确定的就是左子树的长度,这一点可以很简单的从中序遍历中获得,也就是左子树长度 = 根节点下标 - 左子树的左边界 (在中序遍历数组中)

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */

var buildTree = function(preorder, inorder{
    let map = new Map();
    for (let i = 0; i< preorder.length; i++) {
        map.set(inorder[i], i) // 先把中序遍历保存到map中
    }

    return recursive(00, inorder.length - 1);


    /**
     * @param pre_root_idx  先序遍历的索引
     * @param in_left_idx  中序遍历的索引
     * @param in_right_idx 中序遍历的索引
     */

    function recursive(pre_root_idx, in_left_idx, in_right_idx{
        //相等就是自己,左子树的index不可能大于右子树的
        if (in_left_idx > in_right_idx) {
            return null;
        }
        //root_idx是在先序里面的
        let root = new TreeNode(preorder[pre_root_idx]);
        // 有了先序的,再根据先序的,在中序中获 当前根的索引
        let idx = map.get(preorder[pre_root_idx]);

        // 左子树的根节点:左子树的(前序遍历)第一个,就是+1,
        // 左边边界就是left,
        // 右边边界是中间区分的idx-1
        root.left = recursive(pre_root_idx + 1, in_left_idx, idx - 1);
        

        // 由根节点在中序遍历的idx 区分成2段,idx 就是根
        // 右子树的根index = 当前节点根index + 左子树的长度
        // pre_root_idx 当前的根
        // 左子树的长度 = 左子树的长度 = 左子树的左边-右边 (idx-1 - in_left_idx +1)。最后+1就是右子树的根了
        root.right = recursive(pre_root_idx + (idx-1 - in_left_idx +1)  + 1, idx + 1, in_right_idx);
        return root;
    }
};