7. 剑指offer系列——面试题07. 重建二叉树

269 阅读3分钟

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

例如,给出

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

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000

回顾

首先回顾下二叉树的遍历

  • 前序遍历:根结点 —> 左子树 —> 右子树(先遍历根节点,然后左右)
  • 中序遍历:左子树—> 根结点 —> 右子树(在中间遍历根节点)
  • 后序遍历:左子树 —> 右子树 —> 根结点(最后遍历根节点)
  • 层次遍历:按层次遍历

所谓的前序、中序、后续,就是对根节点而言的,左右的遍历顺序不变,前序就是根节点最先遍历,然后左右;中序就是把根节点放在中间遍历;后序则是把根节点放在最后遍历

分析:根据前序和中序遍历的特点,可以得出以下结论

  • 前序遍历的首个元素即为root根节点
  • 在中序遍历中搜索根节点的索引,可以把中序遍历分解为[左子树|根节点|右子树]
  • 根据中序遍历中左/右子树的节点数量,可以将前序遍历划分为[根节点|左子树|右子树]

通过以上结论我们可以确定3个节点的关系

  • 树的根节点
  • 左子树的根节点(前序遍历中左子树的首个元素)
  • 右子树的根节点(前序遍历中右子树的首个元素)

解题思路

树型结构大多使用递归搞定:

  • 递归参数:
    • preRoot:前序遍历的根节点的位置
    • inLeft:中序遍历的左边界的位置
    • inRight:中序遍历的右边界的位置
  • 终止条件: 左边界大于右边界
  • 递归流程:
    • 找到根节点root,root值为前序遍历第一个值
    • 找到root在中序遍历中的位置,此处使用哈希表保存了中序遍历中值与索引的映射关系
    • 递归调用root节点的左子树与右子树
      • 根节点在中序索引中的位置: i
      • 左子树:根节点为前序遍历preRoot + 1,根节点的左右边界为:inLeft 和 i - 1
      • 左子树的长度:i - inleft
      • 右子树:根节点为前序遍历preRoot + 左子树的长度 + 1 = preRoot + i - inleft + 1, 左右边界为:i + 1 和 inRight;

代码

Map<Integer, Integer> dic = new HashMap<>();
int[] po;

public TreeNode buildTree(int[] preorder, int[] inorder) {
    po = preorder;
    for (int i = 0; i < inorder.length; i++) {
        dic.put(inorder[i], i);
    }
    return recur(0, 0, inorder.length - 1);
}

/**
 *
 * @param preRoot   前序遍历的根节点的位置
 * @param inLeft    中序遍历的左边界的位置
 * @param inRight   中序遍历的右边界的位置
 * @return
 */
TreeNode recur(int preRoot, int inLeft, int inRight) {
    // 左边界大于右边界返回
    if (inLeft > inRight) return null;
    // 创建节点
    TreeNode root = new TreeNode(po[preRoot]);
    // 查找根节点在中序遍历中的位置
    int i = dic.get(po[preRoot]);
    // 左子树,前序遍历的根节点 + 1 是左子树的根节点
    root.left = recur(preRoot + 1, inLeft, i - 1);
    // 右子树的根,就是前序遍历右子树的第一个,相当于当前根节点 + 左子树的长度 + 1
    // preRoot是当前的根,i是当前根的位置, 左子树的长度 = i - inLeft
    // 右子树的根 = preRoot + ( i - inLeft ) + 1 = preRoot + i - inLeft + 1
    root.right = recur(preRoot + i - inLeft + 1, i + 1, inRight);
    return root;
}