【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题类似,但使用的是后序遍历而非前序遍历。核心在于理解后序遍历和中序遍历的特点,并通过递归分治的思想重建二叉树。
问题本质
给定中序和后序遍历序列,需要唯一确定并重建这棵二叉树。关键问题:
- 后序遍历特点:左子树 → 右子树 → 根节点(根在最后)
- 中序遍历特点:左子树 → 根节点 → 右子树(根在中间)
- 递归子结构:找到根节点后,可以分割左右子树,递归构造
- 关键区别:与前序相比,后序的根节点在最后,构造顺序需要调整
核心思想
递归分治算法:
- 确定根节点:后序遍历的最后一个元素就是根节点
- 定位根节点:在中序遍历中找到根节点的位置
- 划分子树:根节点位置将中序遍历分为左右两部分
- 递归构造:先构造右子树,再构造左子树(与前序相反)
- 终止条件:当子树为空时返回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]
递归构造详细过程
后序与前序的对比
索引分割示意图
复杂度分析
时间复杂度详解
递归+哈希表: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
}
注意:必须先构造右子树,因为后序是左-右-根
边界情况处理
- 空数组:
inorder=[], postorder=[]→nil - 单节点:
inorder=[1], postorder=[1]→ 单个节点 - 左偏树:所有节点都是左子节点
- 右偏树:所有节点都是右子节点
- 完全二叉树:标准的完全二叉树结构
- 负数节点:
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])
实战技巧总结
- 遍历特点:后序最后一个是根,中序根节点分割左右
- 哈希优化:预处理中序数组,O(1)定位根节点
- 索引计算:仔细计算左右子树的索引范围,尤其是后序
- 构造顺序:使用全局变量时,必须先构造右子树
- 边界检查:起始索引大于结束索引时返回nil
- 避免复制:传递索引而不是切片,减少空间开销
与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
}
应用场景
- 树的序列化:保存和恢复树结构
- 数据恢复:从遍历记录恢复数据结构
- 树的传输:网络传输树结构
- 编译器:抽象语法树的构造和还原
- 表达式树:从后缀表达式构造表达式树
代码实现
本题提供了四种不同的解法,重点掌握递归+哈希表方法。
测试结果
| 测试用例 | 递归+哈希表 | 递归+切片 | 迭代+栈 | 全局变量 |
|---|---|---|---|---|
| 基础测试 | ✅ | ✅ | ✅ | ✅ |
| 单节点测试 | ✅ | ✅ | ✅ | ✅ |
| 左偏树测试 | ✅ | ✅ | ✅ | ✅ |
| 右偏树测试 | ✅ | ✅ | ✅ | ✅ |
| 完全二叉树测试 | ✅ | ✅ | ✅ | ✅ |
核心收获
- 遍历理解:深刻理解前序、中序、后序遍历的区别
- 递归分治:掌握递归构造树的思想
- 细节把控:注意后序遍历根在最后的特点
- 构造顺序:理解为什么全局变量法要先构造右子树
应用拓展
- 从前序和中序构造二叉树(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)
}
