当前位置: 首页 > news >正文

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

文章目录

  • 106. 从中序与后序遍历序列构造二叉树
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 遍历特点详解
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(递归+哈希表)
        • 递归构造详细过程
        • 后序与前序的对比
        • 索引分割示意图
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:递归+哈希表(最优解法)
        • 技巧2:递归+切片(简洁但效率稍低)
        • 技巧3:迭代+栈(避免递归)
        • 技巧4:全局变量优化
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 单节点
        • 左偏树
        • 右偏树
        • 完全二叉树
      • 常见错误与陷阱
        • 错误1:左右子树范围计算错误
        • 错误2:构造顺序错误(全局变量法)
        • 错误3:获取根节点位置错误
        • 错误4:切片索引错误
      • 实战技巧总结
      • 与105题的对比
      • 进阶扩展
        • 扩展1:从前序和后序构造二叉树(不唯一)
        • 扩展2:验证中序和后序的合法性
        • 扩展3:同时支持前序+中序和中序+后序
        • 扩展4:获取所有可能的遍历序列
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

106. 从中序与后序遍历序列构造二叉树

题目描述

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例 1:

    3/ \9  20/  \15   7

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

  -1

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

解题思路

问题深度分析

这是一道经典的二叉树重建问题,与105题类似,但使用的是后序遍历而非前序遍历。核心在于理解后序遍历和中序遍历的特点,并通过递归分治的思想重建二叉树。

问题本质

给定中序和后序遍历序列,需要唯一确定并重建这棵二叉树。关键问题:

  • 后序遍历特点:左子树 → 右子树 → 根节点(根在最后)
  • 中序遍历特点:左子树 → 根节点 → 右子树(根在中间)
  • 递归子结构:找到根节点后,可以分割左右子树,递归构造
  • 关键区别:与前序相比,后序的根节点在最后,构造顺序需要调整
核心思想

递归分治算法

  1. 确定根节点:后序遍历的最后一个元素就是根节点
  2. 定位根节点:在中序遍历中找到根节点的位置
  3. 划分子树:根节点位置将中序遍历分为左右两部分
  4. 递归构造先构造右子树,再构造左子树(与前序相反)
  5. 终止条件:当子树为空时返回nil
遍历特点详解

后序遍历[[左子树后序], [右子树后序], 根节点]

  • 最后一个元素总是根节点
  • 前面是左子树的所有节点
  • 中间是右子树的所有节点

中序遍历[[左子树中序], 根节点, [右子树中序]]

  • 根节点将序列分为两部分
  • 左边是左子树的所有节点
  • 右边是右子树的所有节点

与前序的关键区别

  • 前序:根在最前,先构造左子树
  • 后序:根在最后,要么先左后右,要么先右后左都可以
典型情况分析

情况1:完整示例

中序: [9, 3, 15, 20, 7]
后序: [9, 15, 7, 20, 3]分析:
1. 后序最后一个3是根节点
2. 在中序中找到3的位置(索引1)
3. 中序[9]是左子树,[15,20,7]是右子树
4. 后序[9]是左子树,[15,7,20]是右子树
5. 递归构造左右子树构造过程:3/ \9  20/  \15   7

情况2:单节点

中序: [-1]
后序: [-1]
结果: -1(单个节点)

情况3:左偏树

中序: [3, 2, 1]
后序: [3, 2, 1]
结果:1/
2
/
3

情况4:右偏树

中序: [1, 2, 3]
后序: [1, 2, 3]
结果:
1\2\3
算法对比
算法时间复杂度空间复杂度特点
递归+哈希表O(n)O(n)最优解法,快速定位
递归+线性查找O(n²)O(h)简单但慢,重复查找
迭代+栈O(n)O(n)避免递归,较复杂
递归+索引传递O(n)O(h)空间优化,只传递索引

注:n为节点数,h为树高度。递归+哈希表是最优解法。

算法流程图

主算法流程(递归+哈希表)
graph TDA[开始: buildTree inorder, postorder] --> B[构建哈希表: value → index]B --> C[调用递归函数]C --> D[helper inStart, inEnd, postStart, postEnd]D --> E{postStart > postEnd?}E -->|是| F[返回 nil]E -->|否| G[获取根节点值 rootVal = postorder postEnd]G --> H[创建根节点 root = TreeNode rootVal]H --> I[从哈希表获取根在中序中的位置 rootIndex]I --> J[计算左子树大小 leftSize = rootIndex - inStart]J --> K[递归构造左子树<br/>root.Left = helper inStart, rootIndex-1,<br/>postStart, postStart+leftSize-1]K --> L[递归构造右子树<br/>root.Right = helper rootIndex+1, inEnd,<br/>postStart+leftSize, postEnd-1]L --> M[返回 root]
递归构造详细过程
示例: inorder= 9,3,15,20,7
postorder= 9,15,7,20,3
第一层: 根节点3 后序最后
在中序找到3, 索引=1
左子树中序: 9
右子树中序: 15,20,7
左子树后序: 9
右子树后序: 15,7,20
递归构造左子树: 节点9
递归构造右子树: 根节点20
9是叶子节点, 返回
在中序找到20, 索引=3
左子树中序: 15
右子树中序: 7
递归构造: 节点15
递归构造: 节点7
完成构造
最终树结构:
3
/ \\
9 20
/ \\
15 7
后序与前序的对比
前序 vs 后序
前序遍历: 根-左-右
后序遍历: 左-右-根
根节点在第一个位置
构造顺序: 先左后右
preorder preStart 是根
根节点在最后位置
构造顺序: 可先左后右或先右后左
postorder postEnd 是根
105题: 从前序与中序构造
106题: 从中序与后序构造
索引分割示意图
后序和中序数组的分割关系
后序数组分割
中序数组分割
左子树: postorder postStart ... postStart+leftSize-1
右子树: postorder postStart+leftSize ... postEnd-1
根节点: postorder postEnd
左子树: inorder inStart ... rootIndex-1
根节点: inorder rootIndex
右子树: inorder rootIndex+1 ... inEnd

复杂度分析

时间复杂度详解

递归+哈希表:O(n)

  • 构建哈希表:O(n),遍历中序数组一次
  • 递归构造:O(n),每个节点访问一次
  • 哈希表查找:O(1),每次定位根节点
  • 总时间:O(n) + O(n) = O(n)

递归+线性查找:O(n²)

  • 每次都在中序数组中线性查找根节点:O(n)
  • 总共n个节点:O(n) × O(n) = O(n²)
空间复杂度详解

递归+哈希表:O(n)

  • 哈希表:O(n),存储所有节点的索引
  • 递归栈:O(h),h为树高度
  • 最坏情况(偏斜树):O(n)
  • 最好情况(平衡树):O(log n) + O(n) = O(n)

递归+索引传递:O(h)

  • 不使用哈希表,只传递索引
  • 递归栈:O(h)
  • 但时间复杂度退化为O(n²)

关键优化技巧

技巧1:递归+哈希表(最优解法)
// 递归+哈希表 - 最优解法
func buildTree(inorder []int, postorder []int) *TreeNode {// 构建哈希表:值 -> 索引indexMap := make(map[int]int)for i, val := range inorder {indexMap[val] = i}return helper(inorder, 0, len(inorder)-1, postorder, 0, len(postorder)-1, indexMap)
}func helper(inorder []int, inStart, inEnd int,postorder []int, postStart, postEnd int,indexMap map[int]int) *TreeNode {// 递归终止条件if postStart > postEnd {return nil}// 后序遍历最后一个是根节点rootVal := postorder[postEnd]root := &TreeNode{Val: rootVal}// 在中序遍历中定位根节点rootIndex := indexMap[rootVal]// 左子树大小leftSize := rootIndex - inStart// 递归构造左右子树root.Left = helper(inorder, inStart, rootIndex-1,postorder, postStart, postStart+leftSize-1, indexMap)root.Right = helper(inorder, rootIndex+1, inEnd,postorder, postStart+leftSize, postEnd-1, indexMap)return root
}

优势

  • 时间O(n),最优
  • 哈希表O(1)查找
  • 代码清晰
技巧2:递归+切片(简洁但效率稍低)
// 递归+切片 - 代码简洁
func buildTree2(inorder []int, postorder []int) *TreeNode {if len(postorder) == 0 {return nil}// 根节点(后序最后一个)rootVal := postorder[len(postorder)-1]root := &TreeNode{Val: rootVal}// 在中序中找到根节点位置rootIndex := 0for i, val := range inorder {if val == rootVal {rootIndex = ibreak}}// 递归构造左右子树(使用切片)root.Left = buildTree2(inorder[:rootIndex], postorder[:rootIndex])root.Right = buildTree2(inorder[rootIndex+1:], postorder[rootIndex:len(postorder)-1])return root
}

注意:切片操作会复制数组,增加空间开销

技巧3:迭代+栈(避免递归)
// 迭代+栈 - 避免递归栈溢出
func buildTree3(inorder []int, postorder []int) *TreeNode {if len(postorder) == 0 {return nil}root := &TreeNode{Val: postorder[len(postorder)-1]}stack := []*TreeNode{root}inorderIndex := len(inorder) - 1// 从后向前遍历后序数组for i := len(postorder) - 2; i >= 0; i-- {node := &TreeNode{Val: postorder[i]}parent := stack[len(stack)-1]// 当前节点应该是右子节点if parent.Val != inorder[inorderIndex] {parent.Right = node} else {// 找到应该作为左子节点的位置for len(stack) > 0 && stack[len(stack)-1].Val == inorder[inorderIndex] {parent = stack[len(stack)-1]stack = stack[:len(stack)-1]inorderIndex--}parent.Left = node}stack = append(stack, node)}return root
}

特点

  • 避免递归栈
  • 适合深度很大的树
  • 从后向前遍历
技巧4:全局变量优化
// 使用全局索引变量
var postIndex int
var indexMap map[int]intfunc buildTree4(inorder []int, postorder []int) *TreeNode {postIndex = len(postorder) - 1  // 从后向前indexMap = make(map[int]int)for i, val := range inorder {indexMap[val] = i}return build(postorder, 0, len(inorder)-1)
}func build(postorder []int, left, right int) *TreeNode {if left > right {return nil}rootVal := postorder[postIndex]postIndex--  // 从后向前移动root := &TreeNode{Val: rootVal}rootIndex := indexMap[rootVal]// 注意:后序是左右根,所以要先构造右子树root.Right = build(postorder, rootIndex+1, right)root.Left = build(postorder, left, rootIndex-1)return root
}

注意:必须先构造右子树,因为后序是左-右-根

边界情况处理

  1. 空数组inorder=[], postorder=[]nil
  2. 单节点inorder=[1], postorder=[1] → 单个节点
  3. 左偏树:所有节点都是左子节点
  4. 右偏树:所有节点都是右子节点
  5. 完全二叉树:标准的完全二叉树结构
  6. 负数节点inorder=[-1], postorder=[-1] → 负数节点值

测试用例设计

基础测试
输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出: [3,9,20,null,null,15,7]
说明: 标准的二叉树
单节点
输入: inorder = [-1], postorder = [-1]
输出: [-1]
说明: 只有一个节点
左偏树
输入: inorder = [3,2,1], postorder = [3,2,1]
输出: [1,2,null,3]
说明: 所有节点都在左侧
右偏树
输入: inorder = [1,2,3], postorder = [1,2,3]
输出: [1,null,2,null,3]
说明: 所有节点都在右侧
完全二叉树
输入: inorder = [4,2,5,1,6,3,7], postorder = [4,5,2,6,7,3,1]
输出: [1,2,3,4,5,6,7]
说明: 完整的三层二叉树

常见错误与陷阱

错误1:左右子树范围计算错误
// ❌ 错误:右子树后序范围错误
root.Right = helper(inorder, rootIndex+1, inEnd,postorder, postStart+leftSize, postEnd)
// 包含了根节点!// ✅ 正确:排除根节点
root.Right = helper(inorder, rootIndex+1, inEnd,postorder, postStart+leftSize, postEnd-1)
错误2:构造顺序错误(全局变量法)
// ❌ 错误:先构造左子树
root.Left = build(postorder, left, rootIndex-1)
root.Right = build(postorder, rootIndex+1, right)
// postIndex从后向前,应该先右后左!// ✅ 正确:先构造右子树
root.Right = build(postorder, rootIndex+1, right)
root.Left = build(postorder, left, rootIndex-1)
错误3:获取根节点位置错误
// ❌ 错误:使用前序的方式
rootVal := postorder[postStart]  // 错误!后序根在最后// ✅ 正确:后序最后一个是根
rootVal := postorder[postEnd]
错误4:切片索引错误
// ❌ 错误:右子树切片范围
root.Right = buildTree2(inorder[rootIndex+1:], postorder[rootIndex+1:len(postorder)-1])
// 右子树在后序中的位置不对!// ✅ 正确:左子树大小为rootIndex
root.Right = buildTree2(inorder[rootIndex+1:], postorder[rootIndex:len(postorder)-1])

实战技巧总结

  1. 遍历特点:后序最后一个是根,中序根节点分割左右
  2. 哈希优化:预处理中序数组,O(1)定位根节点
  3. 索引计算:仔细计算左右子树的索引范围,尤其是后序
  4. 构造顺序:使用全局变量时,必须先构造右子树
  5. 边界检查:起始索引大于结束索引时返回nil
  6. 避免复制:传递索引而不是切片,减少空间开销

与105题的对比

特性105题(前序+中序)106题(中序+后序)
根节点位置前序第一个后序最后一个
遍历顺序根-左-右左-右-根
递归顺序先左后右先左或先右都可
全局变量递归postIndex从前向后postIndex从后向前
全局变量构造先左后右必须先右后左
索引计算难度相对简单需要注意右子树范围
代码相似度95%相似,只需改几行-

进阶扩展

扩展1:从前序和后序构造二叉树(不唯一)
// 注意:前序+后序无法唯一确定二叉树(除非是满二叉树)
// 例如:前序[1,2] 后序[2,1] 可以构造:
//   1        1
//  /    或    \
// 2            2
扩展2:验证中序和后序的合法性
// 验证中序和后序是否匹配
func validateTraversal(inorder, postorder []int) bool {if len(inorder) != len(postorder) {return false}// 检查元素是否相同inSet := make(map[int]bool)for _, val := range inorder {inSet[val] = true}for _, val := range postorder {if !inSet[val] {return false}}// 尝试构造树,看是否有异常defer func() {recover()}()buildTree(inorder, postorder)return true
}
扩展3:同时支持前序+中序和中序+后序
// 统一接口
func buildTreeFromTraversals(inorder []int, preorder []int, postorder []int) *TreeNode {if preorder != nil {// 使用前序+中序return buildTreePreIn(preorder, inorder)} else if postorder != nil {// 使用中序+后序return buildTreeInPost(inorder, postorder)}return nil
}
扩展4:获取所有可能的遍历序列
// 从树生成所有三种遍历序列
func getAllTraversals(root *TreeNode) (pre, in, post []int) {var preorder func(*TreeNode)preorder = func(node *TreeNode) {if node == nil { return }pre = append(pre, node.Val)preorder(node.Left)preorder(node.Right)}var inorder func(*TreeNode)inorder = func(node *TreeNode) {if node == nil { return }inorder(node.Left)in = append(in, node.Val)inorder(node.Right)}var postorder func(*TreeNode)postorder = func(node *TreeNode) {if node == nil { return }postorder(node.Left)postorder(node.Right)post = append(post, node.Val)}preorder(root)inorder(root)postorder(root)return
}

应用场景

  1. 树的序列化:保存和恢复树结构
  2. 数据恢复:从遍历记录恢复数据结构
  3. 树的传输:网络传输树结构
  4. 编译器:抽象语法树的构造和还原
  5. 表达式树:从后缀表达式构造表达式树

代码实现

本题提供了四种不同的解法,重点掌握递归+哈希表方法。

测试结果

测试用例递归+哈希表递归+切片迭代+栈全局变量
基础测试
单节点测试
左偏树测试
右偏树测试
完全二叉树测试

核心收获

  1. 遍历理解:深刻理解前序、中序、后序遍历的区别
  2. 递归分治:掌握递归构造树的思想
  3. 细节把控:注意后序遍历根在最后的特点
  4. 构造顺序:理解为什么全局变量法要先构造右子树

应用拓展

  • 从前序和中序构造二叉树(105题)
  • 从层序和中序构造二叉树
  • 表达式树的构造与计算
  • 树的序列化与反序列化的多种实现

完整题解代码

package mainimport ("fmt""strings"
)// TreeNode 二叉树节点定义
type TreeNode struct {Val   intLeft  *TreeNodeRight *TreeNode
}// =========================== 方法一:递归+哈希表(最优解法) ===========================// buildTree 从中序与后序遍历序列构造二叉树
// 时间复杂度:O(n),n为节点数,每个节点访问一次
// 空间复杂度:O(n),哈希表O(n) + 递归栈O(h)
func buildTree(inorder []int, postorder []int) *TreeNode {// 构建哈希表:值 -> 索引,用于快速定位根节点indexMap := make(map[int]int)for i, val := range inorder {indexMap[val] = i}return helper(inorder, 0, len(inorder)-1,postorder, 0, len(postorder)-1, indexMap)
}// helper 递归辅助函数
func helper(inorder []int, inStart, inEnd int,postorder []int, postStart, postEnd int,indexMap map[int]int) *TreeNode {// 递归终止条件if postStart > postEnd {return nil}// 后序遍历最后一个是根节点rootVal := postorder[postEnd]root := &TreeNode{Val: rootVal}// 在中序遍历中定位根节点(O(1)查找)rootIndex := indexMap[rootVal]// 左子树大小leftSize := rootIndex - inStart// 递归构造左右子树// 左子树:中序[inStart, rootIndex-1],后序[postStart, postStart+leftSize-1]root.Left = helper(inorder, inStart, rootIndex-1,postorder, postStart, postStart+leftSize-1, indexMap)// 右子树:中序[rootIndex+1, inEnd],后序[postStart+leftSize, postEnd-1]root.Right = helper(inorder, rootIndex+1, inEnd,postorder, postStart+leftSize, postEnd-1, indexMap)return root
}// =========================== 方法二:递归+切片(简洁版) ===========================// buildTree2 递归+切片,代码简洁但效率稍低
// 时间复杂度:O(n²),线性查找O(n) × 递归n次
// 空间复杂度:O(n²),切片复制导致额外空间
func buildTree2(inorder []int, postorder []int) *TreeNode {if len(postorder) == 0 {return nil}// 根节点(后序最后一个)rootVal := postorder[len(postorder)-1]root := &TreeNode{Val: rootVal}// 在中序中找到根节点位置(线性查找)rootIndex := 0for i, val := range inorder {if val == rootVal {rootIndex = ibreak}}// 递归构造左右子树(使用切片,会复制数组)// 左子树大小为rootIndexroot.Left = buildTree2(inorder[:rootIndex],postorder[:rootIndex])root.Right = buildTree2(inorder[rootIndex+1:],postorder[rootIndex:len(postorder)-1])return root
}// =========================== 方法三:迭代+栈(避免递归) ===========================// buildTree3 迭代+栈实现,从后向前遍历
// 时间复杂度:O(n)
// 空间复杂度:O(n),栈空间
func buildTree3(inorder []int, postorder []int) *TreeNode {if len(postorder) == 0 {return nil}root := &TreeNode{Val: postorder[len(postorder)-1]}stack := []*TreeNode{root}inorderIndex := len(inorder) - 1// 从后向前遍历后序数组for i := len(postorder) - 2; i >= 0; i-- {node := &TreeNode{Val: postorder[i]}parent := stack[len(stack)-1]// 当前节点应该是右子节点if parent.Val != inorder[inorderIndex] {parent.Right = node} else {// 找到应该作为左子节点的位置for len(stack) > 0 && stack[len(stack)-1].Val == inorder[inorderIndex] {parent = stack[len(stack)-1]stack = stack[:len(stack)-1]inorderIndex--}parent.Left = node}stack = append(stack, node)}return root
}// =========================== 方法四:全局变量优化 ===========================var postIndex int
var indexMap map[int]int// buildTree4 使用全局变量优化
// 时间复杂度:O(n)
// 空间复杂度:O(n)
func buildTree4(inorder []int, postorder []int) *TreeNode {postIndex = len(postorder) - 1 // 从后向前indexMap = make(map[int]int)for i, val := range inorder {indexMap[val] = i}return build(postorder, 0, len(inorder)-1)
}func build(postorder []int, left, right int) *TreeNode {if left > right {return nil}rootVal := postorder[postIndex]postIndex-- // 从后向前移动root := &TreeNode{Val: rootVal}rootIdx := indexMap[rootVal]// 注意:后序是左右根,所以要先构造右子树root.Right = build(postorder, rootIdx+1, right)root.Left = build(postorder, left, rootIdx-1)return root
}// =========================== 辅助函数 ===========================// treeToArray 将树转换为数组(层序遍历)
func treeToArray(root *TreeNode) []interface{} {if root == nil {return []interface{}{}}result := []interface{}{}queue := []*TreeNode{root}for len(queue) > 0 {node := queue[0]queue = queue[1:]if node == nil {result = append(result, nil)} else {result = append(result, node.Val)queue = append(queue, node.Left)queue = append(queue, node.Right)}}// 移除末尾的nilfor len(result) > 0 && result[len(result)-1] == nil {result = result[:len(result)-1]}return result
}// printArray 打印数组
func printArray(arr []interface{}) {fmt.Print("[")for i, v := range arr {if i > 0 {fmt.Print(",")}if v == nil {fmt.Print("null")} else {fmt.Print(v)}}fmt.Println("]")
}// visualizeTree 可视化打印树结构
func visualizeTree(root *TreeNode, prefix string, isLeft bool) {if root == nil {return}fmt.Print(prefix)if isLeft {fmt.Print("├── ")} else {fmt.Print("└── ")}fmt.Println(root.Val)if root.Left != nil || root.Right != nil {if root.Left != nil {newPrefix := prefixif isLeft {newPrefix += "│   "} else {newPrefix += "    "}visualizeTree(root.Left, newPrefix, true)}if root.Right != nil {newPrefix := prefixif isLeft {newPrefix += "│   "} else {newPrefix += "    "}visualizeTree(root.Right, newPrefix, false)}}
}// preorderTraversal 前序遍历
func preorderTraversal(root *TreeNode) []int {if root == nil {return []int{}}result := []int{root.Val}result = append(result, preorderTraversal(root.Left)...)result = append(result, preorderTraversal(root.Right)...)return result
}// inorderTraversal 中序遍历
func inorderTraversal(root *TreeNode) []int {if root == nil {return []int{}}result := inorderTraversal(root.Left)result = append(result, root.Val)result = append(result, inorderTraversal(root.Right)...)return result
}// postorderTraversal 后序遍历
func postorderTraversal(root *TreeNode) []int {if root == nil {return []int{}}result := postorderTraversal(root.Left)result = append(result, postorderTraversal(root.Right)...)result = append(result, root.Val)return result
}// treesEqual 判断两棵树是否相等
func treesEqual(t1, t2 *TreeNode) bool {if t1 == nil && t2 == nil {return true}if t1 == nil || t2 == nil {return false}if t1.Val != t2.Val {return false}return treesEqual(t1.Left, t2.Left) && treesEqual(t1.Right, t2.Right)
}// =========================== 扩展功能 ===========================// getAllTraversals 从树生成所有三种遍历序列
func getAllTraversals(root *TreeNode) (pre, in, post []int) {return preorderTraversal(root), inorderTraversal(root), postorderTraversal(root)
}// validateTraversal 验证中序和后序是否匹配
func validateTraversal(inorder, postorder []int) bool {if len(inorder) != len(postorder) {return false}// 检查元素是否相同inSet := make(map[int]bool)for _, val := range inorder {inSet[val] = true}for _, val := range postorder {if !inSet[val] {return false}}return true
}// serialize 将树序列化为中序和后序
func serialize(root *TreeNode) ([]int, []int) {return inorderTraversal(root), postorderTraversal(root)
}// deserialize 反序列化
func deserialize(inorder, postorder []int) *TreeNode {return buildTree(inorder, postorder)
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 106: 从中序与后序遍历序列构造二叉树 ===\n")// 测试用例testCases := []struct {name      stringinorder   []intpostorder []intexpectArr []interface{}}{{name:      "示例1: 标准二叉树",inorder:   []int{9, 3, 15, 20, 7},postorder: []int{9, 15, 7, 20, 3},expectArr: []interface{}{3, 9, 20, nil, nil, 15, 7},},{name:      "示例2: 单节点",inorder:   []int{-1},postorder: []int{-1},expectArr: []interface{}{-1},},{name:      "左偏树",inorder:   []int{3, 2, 1},postorder: []int{3, 2, 1},expectArr: []interface{}{1, 2, nil, 3},},{name:      "右偏树",inorder:   []int{1, 2, 3},postorder: []int{1, 2, 3},expectArr: []interface{}{1, nil, 2, nil, 3},},{name:      "完全二叉树",inorder:   []int{4, 2, 5, 1, 6, 3, 7},postorder: []int{4, 5, 2, 6, 7, 3, 1},expectArr: []interface{}{1, 2, 3, 4, 5, 6, 7},},{name:      "不平衡树",inorder:   []int{8, 4, 9, 2, 5, 1, 6, 3, 7},postorder: []int{8, 9, 4, 5, 2, 6, 7, 3, 1},expectArr: []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9},},{name:      "负数节点",inorder:   []int{-2, -1, -3},postorder: []int{-2, -3, -1},expectArr: []interface{}{-1, -2, -3},},}methods := []struct {name stringfn   func([]int, []int) *TreeNode}{{"方法一:递归+哈希表", buildTree},{"方法二:递归+切片", buildTree2},{"方法三:迭代+栈", buildTree3},{"方法四:全局变量", buildTree4},}// 对每种方法运行测试for _, method := range methods {fmt.Printf("\n%s\n", method.name)fmt.Println(strings.Repeat("=", 60))passCount := 0for i, tc := range testCases {root := method.fn(tc.inorder, tc.postorder)result := treeToArray(root)// 验证结果status := "✅"if !arraysEqual(result, tc.expectArr) {status = "❌"} else {passCount++}fmt.Printf("  测试%d: %s\n", i+1, status)fmt.Printf("    名称: %s\n", tc.name)fmt.Printf("    中序: %v\n", tc.inorder)fmt.Printf("    后序: %v\n", tc.postorder)fmt.Printf("    输出: ")printArray(result)if !arraysEqual(result, tc.expectArr) {fmt.Printf("    期望: ")printArray(tc.expectArr)}// 为第一个示例打印树结构if i == 0 {fmt.Println("    树结构:")if root != nil {visualizeTree(root, "      ", false)}}// 验证遍历结果in := inorderTraversal(root)post := postorderTraversal(root)fmt.Printf("    验证中序: %v (匹配: %v)\n", in, slicesEqual(in, tc.inorder))fmt.Printf("    验证后序: %v (匹配: %v)\n", post, slicesEqual(post, tc.postorder))}fmt.Printf("\n  通过: %d/%d\n", passCount, len(testCases))}// 扩展功能测试fmt.Println("\n\n=== 扩展功能测试 ===\n")testExtensions()// 与105题对比fmt.Println("\n=== 与105题对比 ===\n")compareWith105()// 性能对比fmt.Println("\n=== 性能对比 ===\n")performanceTest()
}// arraysEqual 比较两个interface数组是否相等
func arraysEqual(a, b []interface{}) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// slicesEqual 比较两个int切片是否相等
func slicesEqual(a, b []int) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// testExtensions 测试扩展功能
func testExtensions() {fmt.Println("1. 获取所有遍历序列")root := buildTree([]int{4, 2, 5, 1, 6, 3, 7}, []int{4, 5, 2, 6, 7, 3, 1})pre, in, post := getAllTraversals(root)fmt.Printf("   前序: %v\n", pre)fmt.Printf("   中序: %v\n", in)fmt.Printf("   后序: %v\n", post)fmt.Println("   树结构:")visualizeTree(root, "     ", false)fmt.Println("\n2. 序列化与反序列化")original := buildTree([]int{9, 3, 15, 20, 7}, []int{9, 15, 7, 20, 3})inSeq, postSeq := serialize(original)fmt.Printf("   序列化中序: %v\n", inSeq)fmt.Printf("   序列化后序: %v\n", postSeq)restored := deserialize(inSeq, postSeq)fmt.Printf("   反序列化: ")printArray(treeToArray(restored))fmt.Printf("   树相等: %v\n", treesEqual(original, restored))fmt.Println("\n3. 验证遍历合法性")fmt.Printf("   合法: %v\n", validateTraversal([]int{9, 3, 15, 20, 7}, []int{9, 15, 7, 20, 3}))fmt.Printf("   不合法: %v\n", validateTraversal([]int{1, 2, 3}, []int{1, 2}))
}// compareWith105 与105题对比
func compareWith105() {fmt.Println("使用同一棵树,对比前序+中序和中序+后序构造的结果")// 构造一棵树inorder := []int{4, 2, 5, 1, 6, 3, 7}postorder := []int{4, 5, 2, 6, 7, 3, 1}tree := buildTree(inorder, postorder)// 获取前序preorder := preorderTraversal(tree)fmt.Printf("  中序遍历: %v\n", inorder)fmt.Printf("  前序遍历: %v\n", preorder)fmt.Printf("  后序遍历: %v\n", postorder)fmt.Println("\n  关键区别:")fmt.Println("  - 105题: 前序第一个是根节点,从前往后遍历")fmt.Println("  - 106题: 后序最后一个是根节点,从后往前遍历")fmt.Println("  - 全局变量法: 105题先左后右,106题必须先右后左")fmt.Println("\n  树结构:")visualizeTree(tree, "    ", false)
}// performanceTest 性能测试
func performanceTest() {// 构建深度为10的完全二叉树size := (1 << 10) - 1 // 2^10 - 1 = 1023个节点// 生成完全二叉树的中序和后序inorder := make([]int, size)postorder := make([]int, size)// 中序:左-根-右(升序)for i := 0; i < size; i++ {inorder[i] = i + 1}// 后序:左-右-根postIdx := 0var genPostorder func(int, int)genPostorder = func(start, end int) {if start > end {return}mid := (start + end) / 2genPostorder(start, mid-1)genPostorder(mid+1, end)postorder[postIdx] = midpostIdx++}genPostorder(1, size)fmt.Printf("测试数据:完全二叉树,节点数=%d,深度=10\n\n", size)fmt.Println("各方法性能测试:")root1 := buildTree(inorder, postorder)fmt.Printf("  方法一(递归+哈希表): 节点数=%d\n", countNodes(root1))root2 := buildTree2(inorder, postorder)fmt.Printf("  方法二(递归+切片): 节点数=%d\n", countNodes(root2))root3 := buildTree3(inorder, postorder)fmt.Printf("  方法三(迭代+栈): 节点数=%d\n", countNodes(root3))root4 := buildTree4(inorder, postorder)fmt.Printf("  方法四(全局变量): 节点数=%d\n", countNodes(root4))fmt.Println("\n说明:")fmt.Println("  - 方法一(递归+哈希表):O(n)时间,O(n)空间,最优解法")fmt.Println("  - 方法二(递归+切片):O(n²)时间,O(n²)空间,简洁但低效")fmt.Println("  - 方法三(迭代+栈):O(n)时间,O(n)空间,避免递归")fmt.Println("  - 方法四(全局变量):O(n)时间,O(n)空间,注意构造顺序")
}// countNodes 计算节点总数
func countNodes(root *TreeNode) int {if root == nil {return 0}return 1 + countNodes(root.Left) + countNodes(root.Right)
}
http://www.dtcms.com/a/589519.html

相关文章:

  • 手机怎么建自己的网站管理网络的网站
  • SpringCloud02-服务拆分远程调用
  • JavaScript判空最佳实践
  • 做的网站缩小内容就全乱了珠海网站建设官网
  • 突破AI助手成本壁垒:知识图谱思维架构让小模型实现大性能
  • 做网站使用明星照片可以吗保护动物网站建设策划书
  • 【Linux学习】新建系统(Ubuntu)后的一些开局必要操作配置
  • 十大免费网站推广网络规划工程师
  • Java 大视界 -- Java 大数据机器学习模型在电商用户流失预测与留存策略制定中的应用
  • 山东网站开发苏州建设工程招标在哪个网站
  • 网站费用单企业速成网站
  • 电子商务网站建设的意义0基础怎么做网站模版
  • 深入理解C语言共用体/联合体(union):大小计算与大小端判断实战
  • ITIL 4 测评题库试卷及详细分析
  • 数据库基础-01Mysql库和表的操作
  • linux服务ping不通百度的解决过程【ping www.baidu.comping: unknown host】
  • 广州网站建设模板设计素材库
  • 深入浅出蓝桥杯:算法基础概念与实战应用(一)基础算法(上)
  • C++ vector 全面解析:从接口使用到底层机制
  • 亚马逊欧洲站vat怎么申请湖南企业做网站
  • vite-plugin-vue-mcp:在 Vue 3 + Vite 中启用 MCP,让 AI 理解并调试你的应用
  • 如何20元/年开通Termius专业版
  • 树莓派docker_freeCAD环境搭建
  • 数字营销网站建设佛山规划建设局网站
  • 【数据结构】位图和布隆过滤器
  • 对于数据结构:堆的超详细保姆级解析——下(堆排序以及TOP-K问题)
  • (* MARK_DEBUG=“true“ *)
  • 章丘哪里做网站做商城网站产品怎么分布
  • 使用docker部署Java项目
  • PyTorch深度学习进阶(三)(残差网络ResNet)