正题
105. 从前序与中序遍历序列构造二叉树
给定一棵树的前序遍历 preorder
与中序遍历 inorder
。请构造二叉树并返回其根节点。
示例 1:
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
};
复制代码
正题过程较抽象,也不好用动画表示,只有对二叉树的前序和中序遍历比较敏感才比较更容易理解。