【LeetCode】105. 从前序与中序遍历序列构造二叉树

leetcode

 

解题思路

根据二叉树的前序遍历和中序遍历序列构建二叉树的核心在于​​利用两种遍历的特性定位根节点和子树边界

  1. ​​前序遍历​​:首个元素是根节点,随后是左子树节点,最后是右子树节点
  2. ​​中序遍历​​:根节点左侧是左子树节点,右侧是右子树节点
  3. ​​分治策略​​:通过根节点在中序序列的位置确定左右子树边界,递归构建子树

关键步骤

  1. ​​终止条件​​:当遍历序列为空时返回 nil
  2. ​​根节点定位​​:前序序列首元素即为根节点值
  3. ​​划分子树​​:
    • 在中序序列中找到根节点位置 rootIndex
    • 计算左子树节点数 leftNum = rootIndex - inStart
  4. ​​递归构建​​:
    • 左子树:前序范围 [preStart+1, preStart+leftNum],中序范围 [inStart, rootIndex-1]
    • 右子树:前序范围 [preStart+leftNum+1, preEnd],中序范围 [rootIndex+1, inEnd]

代码实现

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func buildTree(preorder []int, inorder []int) *TreeNode {
    if len(preorder) == 0 {
        return nil
    }
    // 构建中序索引映射
    indexMap := make(map[int]int)
    for i, v := range inorder {
        indexMap[v] = i
    }

    return helper(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1, indexMap)
}

func helper(preorder []int, preStart, preEnd int, inorder []int, inStart, inEnd int, indexMap map[int]int) *TreeNode {
    if preStart > preEnd {
        return nil
    }

    // 前序首元素为根节点
    rootVal := preorder[preStart]
    root := &TreeNode{Val: rootVal}

    // 获取中序根节点位置
    rootIndex := indexMap[rootVal]
    leftNum := rootIndex - inStart // 左子树节点树

    // 递归构建子树
    root.Left = helper(preorder, preStart+1, preStart+leftNum, inorder, inStart, rootIndex-1, indexMap)
    root.Right = helper(preorder, preStart+leftNum+1, preEnd, inorder, rootIndex+1, inEnd, indexMap)

    return root
}

代码解析

  1. ​​索引映射​​:

    • 构建 inorder 值到索引的哈希映射,将根节点查找从 O(n) 优化到 O(1)
  2. ​​递归函数​​:

    • ​​终止条件​​:当子树序列为空时返回 nil
    • ​​根节点创建​​:前序首元素构建根节点
    • ​​子树边界计算​​:
      • leftNum = rootIndex - inStart 确定左子树节点数
      • 左子树前序范围:[preStart+1, preStart+leftNum]
      • 右子树前序范围:[preStart+leftNum+1, preEnd]

示例测试

// 按层打印二叉树, 测试使用
func printTree(root *TreeNode) {
    if root == nil {
        fmt.Println("[]")
        return
    }
    queue := []*TreeNode{root}
    var res []interface{}

    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        if node == nil {
            res = append(res, nil)
        } else {
            res = append(res, node.Val)
            queue = append(queue, node.Left, node.Right)
        }
    }

    // 移除末尾 nil
    i := len(res) - 1
    for i >= 0 && res[i] == nil {
        i--
    }
    fmt.Println(res[:i+1])
}

func main() {
    // 示例1: pre=[3,9,20,15,7], in=[9,3,15,20,7]
    root1 := buildTree(
        []int{3, 9, 20, 15, 7},
        []int{9, 3, 15, 20, 7},
    )
    printTree(root1) // [3 9 20 nil nil 15 7]

    // 示例2: pre=[-1], in=[-1]
    root2 := buildTree([]int{-1}, []int{-1})
    printTree(root2) // [-1]
}

复杂度分析

指标说明
​​时间复杂度​​ O(n) 每个节点只处理1次
​​空间复杂度​​ O(n) 哈希表存储n个索引,递归栈深度O(h)(h为树高)

关键点

  1. ​​分治策略​​:将问题分解为左/右子树构建子问题
  2. ​​索引优化​​:哈希表加速中序根节点定位(从O(n)→O(1))
  3. ​​边界处理​​:
    • 左子树节点数 leftNum = rootIndex - inStart
    • 右子树前序起始 preStart + leftNum + 1
  4. ​​完全二叉树特性​​:中序根节点左侧必为左子树
posted @ 2025-06-07 12:00  云隙之间  阅读(40)  评论(0)    收藏  举报