重建二叉树

根据前序遍历和中序遍历结果重建二叉树

问题

给定二叉树的前序遍历和中序遍历结果,重建该二叉树。假设输入的前序遍历和中序遍历结果中都不含重复的数字。

例如:

  • 前序遍历:[3, 9, 20, 15, 7]
  • 中序遍历:[9, 3, 15, 20, 7]

重建出的二叉树:

    3
   / \
  9  20
    /  \
   15   7

解答

思路

  • 前序遍历:根 → 左 → 右,第一个元素是根节点
  • 中序遍历:左 → 根 → 右,根节点左边是左子树,右边是右子树

通过前序找到根节点,再在中序中定位根节点位置,从而划分左右子树,递归构建。

实现

// 二叉树节点
class TreeNode {
  constructor(val) {
    this.val = val;
    this.left = null;
    this.right = null;
  }
}

/**
 * 根据前序和中序遍历重建二叉树
 * @param {number[]} preorder - 前序遍历结果
 * @param {number[]} inorder - 中序遍历结果
 * @return {TreeNode}
 */
function buildTree(preorder, inorder) {
  // 构建中序遍历的值到索引的映射,加速查找
  const inorderMap = new Map();
  inorder.forEach((val, index) => inorderMap.set(val, index));

  // 递归构建
  function build(preStart, preEnd, inStart, inEnd) {
    // 递归终止条件
    if (preStart > preEnd) return null;

    // 前序遍历的第一个元素是根节点
    const rootVal = preorder[preStart];
    const root = new TreeNode(rootVal);

    // 在中序遍历中找到根节点位置
    const rootIndex = inorderMap.get(rootVal);

    // 计算左子树的节点数量
    const leftSize = rootIndex - inStart;

    // 递归构建左子树
    // 前序:[preStart+1, preStart+leftSize]
    // 中序:[inStart, rootIndex-1]
    root.left = build(
      preStart + 1,
      preStart + leftSize,
      inStart,
      rootIndex - 1
    );

    // 递归构建右子树
    // 前序:[preStart+leftSize+1, preEnd]
    // 中序:[rootIndex+1, inEnd]
    root.right = build(
      preStart + leftSize + 1,
      preEnd,
      rootIndex + 1,
      inEnd
    );

    return root;
  }

  return build(0, preorder.length - 1, 0, inorder.length - 1);
}

测试

// 测试
const preorder = [3, 9, 20, 15, 7];
const inorder = [9, 3, 15, 20, 7];

const tree = buildTree(preorder, inorder);

// 验证:层序遍历输出
function levelOrder(root) {
  if (!root) return [];
  const result = [];
  const queue = [root];
  while (queue.length) {
    const node = queue.shift();
    result.push(node.val);
    if (node.left) queue.push(node.left);
    if (node.right) queue.push(node.right);
  }
  return result;
}

console.log(levelOrder(tree)); // [3, 9, 20, 15, 7]

简洁版本

function buildTree(preorder, inorder) {
  if (!preorder.length) return null;

  const rootVal = preorder[0];
  const root = new TreeNode(rootVal);
  const mid = inorder.indexOf(rootVal);

  root.left = buildTree(
    preorder.slice(1, mid + 1),
    inorder.slice(0, mid)
  );
  root.right = buildTree(
    preorder.slice(mid + 1),
    inorder.slice(mid + 1)
  );

  return root;
}

关键点

  • 前序遍历第一个元素是根节点,中序遍历中根节点左边是左子树、右边是右子树
  • 用 Map 缓存中序遍历的索引,将查找从 O(n) 优化到 O(1)
  • 通过索引划分避免 slice 创建新数组,降低空间复杂度
  • 时间复杂度 O(n),空间复杂度 O(n)(Map + 递归栈)