【路飞】前序与中序遍历序列构造二叉树

193 阅读3分钟

记录 1 道算法题

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

leetcode-cn.com/problems/co…


首先,前序遍历是根左右,中序遍历是左根右。就是前序遍历可以整体看为[根][左][右],中序遍历整体看为[左][根][右]。

         a
      b     e
    c   d

我们知道前序遍历一定是根节点开头的,例如:a, b, c, d, e,a是根节点,后面的bc可能是左子节点也可能不是。这时候就要与中序遍历进行匹配了。

中序遍历是 c, b, d, a, e

这时候,我们知道某一个根节点,根据中序遍历的规则,根节点的左边就是它的左子节点。这时候我们可以看到 a 节点 左边有 3个节点。说明 节点a 他的左子树及其孩子有 3 个。说明前序遍历里面, 节点a 后面的 b,c,d都是在 节点a 的左边。按照 [根][左][右] 的左右来分,则 节点a 的右边的节点就是中序遍历里面的 a 的 下标 +1, 即 e 的位置,下标为 4。

补充一个细节,就是节点个数是相等的,所以前序遍历和中序遍历的结果存放数组的长度是相等的。

    a  b  c  d  e
   [x  [  左  ] [右]]
    c  b  d  a  e
   [[  左  ] x  [右]]

然后观察一下根节点改变之后有什么变化,在上面的左区间里面,找到新的根节点。当 b 被看作根节点的时候。

    a  b    c   d     e
      [x  [左][右]]
    c  b  d     a     e
  [[左]x [右]]

当继续在左区间里面找新的根节点的时候,就是 节点c 。我们会发现 节点c 在中序遍历里面已经没有左子树了。就说明当 中序遍历的根节点的 left 为空的时候,左边的树已经恢复了。就是 [左] [根] [右] 的左已经到尽头了,要找中和右了。

那右边的是怎样的呢。在区间中,出去根节点和左边,剩下的就是右子树及其节点。从上面可以看到,当区间缩小到只剩下 1个节点 的时候,right 就确定了。当然也有可能是没有右节点。这是递归到了最里面要终止开始逐层返回的时候。

看到这种区间一直缩小的,就很适合用递归。用下标来标记区间。

为了避免每次都要在中序遍历里面找根节点的下标,有一个优化就是先遍历一次中序遍历数组,然后记录下每一个 val的下标,到时候之间取。

    function buildTree(preorder, inorder) {
        const map = {}
        // 记录下标
        for (let i = 0; i < inorder.length; i++) {
          map[inorder[i]] = i
        }
        
        const create = (pre, ino, preStart, preEnd, inoStart, inoEnd) => {
            // 终止条件,就是区间没有的时候,就是开始下标和结尾下标错开
            if (inoStart > inoEnd) {
                return null
            }
            // 找到根 和 中序遍历中 根的下标
            const rootVal = pre[preStart]
            const rootIdx = map[rootVal]
            const root = new TreeNode(rootVal)
            // 中序遍历左子树的个数
            // 中序遍历的 [左] 区间肯定等于 0,[根下标]
            // 前序遍历对应的区间则是 [根下标], count + 根下标
            // 区间内容数量是一样的。
            const count = rootIdx - inoStart
            // 递归开始
            // create 指定一个新的根节点
            // 个数 = 结尾下标 - 开始下标 - 1 根据这个公式计算区间的结尾
            // [左] [根]
            root.left = create(pre, ino, preStart + 1, preStart + count,inoStart, rootIdx - 1)
            
            root.right = create(pre, ino, preStart + count + 1, preEnd, rootIdx + 1, inoEnd)
            
            // 返回最开头的根节点
            return create(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1)
        }
    }