[路飞]算法:105. 从前序与中序遍历序列构造二叉树

1,672 阅读3分钟

正题

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

给定一棵树的前序遍历 preorder 与中序遍历  inorder。请构造二叉树并返回其根节点。

示例 1:

image.png

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

解析:

首先了解一下二叉树前序和中序遍历的顺序:

前序: 根 -> 左 -> 右

中序: 左 -> 根 -> 右

寻找切入点

要实现一个二叉树,最首先的是要找到根节点,然后再去通过递归的方式去递归出左右节点。根据前序遍历的遍历顺序我们可以知道,前序遍历的第一个节点即为 根节点,所以我们可以认为,在构造二叉树的过程中,我们可以将前序遍历的 第一个节点作为递归的根节点。

所以要解决该问题的底层逻辑就是找出:分别找出左右节点的前序遍历数组进行递归。

找到规律

以示例1作参考,当前前序遍历第一位是 3,那么该树节点第一个节点一定就是3,是前序遍历的特点。那么他的左树根节点是什么呢?第二位是9,根据前序遍历的原则,左树理论上可能是9,但是如果左侧没有根节点,那么这个9就可能是右树的根节点。所以我们要从中序遍历中提取信息,来判断第二位9是左还是右。

划重点:根据中序遍历的原则,前序遍历的第一位3,在中序遍历中排在第二,那么说明在中序遍历中第二位之前都是根节点3左树下的

于是我们可以在中序遍历中提取出3作为根节点时,左树所有节点,同理剩下的全部都是右树节点了。

提取左右节点再次做为根节点递归

首先我们找出前序遍历的根节点在右树中的位置(用来提取所有左节点和右节点)。

const rootIndex = inorder.indexOf(preorder[0])

以上代码怎么理解呢,左树第一个节点一定是根,rootIndex即为中序遍历中根节点的位置,那么在中序遍历中下标小于rootIndex的都是左树,等于 rootIndex为根,大于 rootIndex的都是右树。所以我们可以通过递归,再次进行左右的迭代。

左树

我们可以进行递归,再一次构造一个以左节点作为根节点的二叉树:

res.left = buildTree(preorder.slice(1,rootIndex + 1), inorder.slice(0, rootIndex))

preorder.slice(1,rootIndex + 1: 去掉已经做为根节点的元素,将rootIndex左侧作为根节点,进行递归

inorder.slice(0, rootIndex): 将 rootIndex 左侧全部提取,作为右节点的所有元素,进行递归。

右树

构造右节点二叉树:

    res.right = buildTree(preorder.slice(rootIndex + 1), inorder.slice(rootIndex + 1))

preorder.slice(rootIndex + 1: 除去根节点之后,左节点剩下的都是右节点。

inorder.slice(rootIndex + 1): rootIndex 右边全是右节点

完整代码:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
  if (inorder.length === 0) return null
    const res = new TreeNode(preorder[0])
    const rootIndex = inorder.indexOf(preorder[0])
    res.left = buildTree(preorder.slice(1,rootIndex + 1), inorder.slice(0, rootIndex))
    res.right = buildTree(preorder.slice(rootIndex + 1), inorder.slice(rootIndex + 1))
    return res
};

image.png

正题过程较抽象,也不好用动画表示,只有对二叉树的前序和中序遍历比较敏感才比较更容易理解。