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

60 阅读3分钟

前言

二叉树解题的思维模式分两类:

1、是否可以通过遍历一遍二叉树得到答案

如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。

2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案

如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。

无论使用哪种思维模式,你都需要思考:如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。

二叉树的构造问题一般都是使用「分解问题」的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树

题解

leetcode.cn/problems/co…

image.png

解法一:基于分解子问题思维模式的递归

首先,肯定要想办法确定根节点的值,把根节点做出来,然后递归构造左右子树即可

前序遍历的第一个值 preorder[0] 就是根节点的值。

找到中序遍历数组中的根节点位置,左右两个子数组即为左右子树的中序遍历结果 image.png

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func buildTree(preorder []int, inorder []int) *TreeNode {
    return build(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1)
}
// build 函数定义:
// 若前序遍历数组为 preorder[preStart..preEnd],
// 中序遍历数组为 inorder[inStart..inEnd],
// 构造二叉树,返回该二叉树的根节点
func build(preorder []int, preStart, preEnd int, inorder []int, inStart, inEnd int) *TreeNode{
    if preStart > preEnd{ // base case
        return nil 
    }
    rootVal := preorder[preStart] // 前序遍历数组第一个就是根节点
    rootInorderIndex := 0 // 寻找root在中序遍历数组的位置
    for idx, v := range inorder{
        if v == rootVal{
            rootInorderIndex = idx
            break
        }
    }
    root := &TreeNode{Val: rootVal}
    leftSize := rootInorderIndex - inStart // 左子树的节点数
    // 递归构造左右子树
    root.Left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, rootInorderIndex-1)
    root.Right = build(preorder, preStart+leftSize+1, preEnd, inorder, rootInorderIndex+1, inEnd)
    return root
}
  • 时间复杂度:O(n^2),每次递归调用都需要在中序数组中搜索根节点位置,对于每个节点,都会递归执行一遍
  • 空间复杂度:O(n),递归栈深度

解法二: 空间换时间优化

通过 for 循环遍历的方式去确定 rootInorderIndex 效率不算高,可以进一步优化,因为题目说二叉树节点的值不存在重复,所以可以使用一个 HashMap 存储元素到索引的映射,这样就可以直接通过 HashMap 查到 rootVal 对应的 rootInorderIndex

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func buildTree(preorder []int, inorder []int) *TreeNode {
    inorderMap := make(map[int]int) // {val: index}
    for idx, v := range inorder{
        inorderMap[v] = idx
    }
    return build(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1, inorderMap)
}

func build(preorder []int, preStart, preEnd int, inorder []int, inStart, inEnd int, inorderMap map[int]int) *TreeNode{
    if preStart > preEnd{ // base case
        return nil 
    }
    rootVal := preorder[preStart] // 前序遍历数组第一个就是根节点
    rootInorderIndex := inorderMap[rootVal] // 寻找root在中序遍历数组的位置
    root := &TreeNode{Val: rootVal}
    leftSize := rootInorderIndex - inStart // 左子树的节点数
    // 递归构造左右子树
    root.Left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, rootInorderIndex-1, inorderMap)
    root.Right = build(preorder, preStart+leftSize+1, preEnd, inorder, rootInorderIndex+1, inEnd, inorderMap)
    return root
}
  • 时间复杂度:O(n),利用哈希将每次递归调用中搜索rootInorderIndex的时间从 O(n)  降至 O(1),只需要考虑递归层级
  • 空间复杂度:O(n),递归栈 + 哈希表