[路飞]_leetcode刷题_105. 从前序与中序遍历序列构造二叉树

170 阅读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]

思路:

递归法。

首先,我们可以知道

  • 前序遍历的头节点是根节点,然后是左子树,然后是右子树

  • 中序遍历的开始一段是左子树,然后是根节点,然后是右子树

我们要弄清楚的就是,

  • 具体前序遍历的左子树和右子树的准确的起始和结束位置

  • 具体中序遍历的左子树的结束位置,根节点的位置,以及右子树的起始结束位置

image.png

那么我们假设

  1. 前序遍历的根节点下标为pre_left, 右子树的末尾下表为pre_right

  2. 中序遍历的左子树开始为in_left,根节点为in_root,右子树末尾为in_right

  3. 根据pre_left的值,可以通过中序遍历indexOf找到中序遍历根节点in_root的下标。

  4. 然后得到中序遍历左子树末尾的下表为in_root-1,右子树起始位in_root+1

  5. 由于前序和中序的左子树的长度是一致的,那么可以得到前序遍历的左子树起始位pre_left+1,末尾为in_root - in_left - pre_left,右子树的起始为in_root - in_left - pre_left+1

然后我们就可以通过递归函数的去分别构造左子树和右子树

image.png

具体的创建子树的递归函数逻辑如下

  1. 递归函数接受参数为,完整的前序遍历和中序遍历数组preorder、inorder,子树的前序遍历起始pre_left、末尾pre_right,子树的中序遍历起始in_left、末尾in_right

  2. 递归结束条件为,pre_left > pre_right,这代表子树一个节点也没有了,直接返回null即可

  3. 通过子树前序遍历起始位置pre_left,创建根节点root = new TreeNode(preorder[pre_left])

  4. 通过pre_left对应的值,找到中序遍历根节点位置in_root = inorder.indexOf(preorder[pre_left])

  5. 通过中序遍历起始in_left和根节点位置in_root计算出新的左子树的长度size = in_root - in_left

  6. 那么可以算出新的左子树的前序起始pre_left+1、末尾pre_left + size,中序起始in_left、末尾in_root-1

  7. 新的右子树的前序起始pre_left + size + 1,末尾pre_rifht,中序起始in_root + 1,末尾in_right

  8. 递归调用自身去创建新的左子树和右子树,并挂在到所对应的root节点上

  9. 最终返回root节点

  10. 第一次调用该递归函数的入参分别为preorder,inorder(这俩已知),前序起始0,末尾preorder.length-1,中序起始0,末尾inorder.length-1

代码如下:

 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
    // 算出节点数量len
    let len = preorder.length;
    return myBuildTree(preorder, inorder, 0, len-1, 0, len-1)
};

function myBuildTree(preorder, inorder, pre_left, pre_right, in_left, in_right) {
   // 若pre_left > pre_right,说明子树为空,直接返回null
   if(pre_left > pre_right){
       return null;
   }
   // 通过pre_left的值创建子树的root节点
   let root = new TreeNode(preorder[pre_left]);
   // 通过pre_left的值,在inorder中找出他的下标
   let in_root = inorder.indexOf(preorder[pre_left]);
   // 算出左子树的长度,这样即可定位出所有需要的下标
   let size = in_root - in_left;

   // 递归创建左子树,并连接到当前根节点
   root.left = myBuildTree(preorder, inorder, pre_left + 1, pre_left + size, in_left, in_root - 1);
   // 递归创建右子树,并连接到当前根节点
   root.right = myBuildTree(preorder, inorder, pre_left + size + 1,pre_right,in_root + 1, in_right)

   return root
}