文章目录
- [94. 二叉树的中序遍历](#94. 二叉树的中序遍历)
94. 二叉树的中序遍历
题目描述
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
- 树中节点数目在范围 [0, 100] 内
- -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解题思路
问题深度分析
本题要求输出二叉树的中序遍历序列(Left -> Root -> Right)。属于二叉树遍历的基础题,但可扩展为多种实现:递归、迭代(显式栈)、统一迭代(颜色标记)、以及 Morris 遍历(O(1) 额外空间)。
- 中序本质:访问顺序为左子树 -> 根节点 -> 右子树。
- 难点:在不使用递归的情况下如何"回溯"(用栈或临时线索)。
核心方法对比
- 方法一(递归):最直观,代码最短,空间为递归栈O(h)。
- 方法二(显式栈迭代):用栈模拟系统递归栈,通用稳定。
- 方法三(颜色标记统一迭代):将"访问/展开"统一成一个模板,适合多种遍历统一写法。
- 方法四(Morris 遍历):利用线索二叉树思想,空间O(1),不需要栈与递归,但会暂时修改树指针(遍历过程中会复原)。
算法流程图
递归(中序)
graph TD
A[Inorder(root)] --> B{root==nil?}
B -->|是| C[return]
B -->|否| D[Inorder(root.Left)]
D --> E[输出 root.Val]
E --> F[Inorder(root.Right)]
显式栈迭代
是 是 否 否 stack空, cur=root cur!=nil or stack不空? cur!=nil? 压栈cur, cur=cur.Left 出栈node 输出node.Val cur=node.Right 结束
Morris 遍历
否 是 是 否 cur=root cur是否有左子树? 输出cur.Val, cur=cur.Right pre=cur.Left的最右节点 pre.Right==nil? pre.Right=cur, cur=cur.Left pre.Right=nil, 输出cur.Val, cur=cur.Right
复杂度分析
- 时间复杂度:四种方法均为 O(n),n 为节点数。
- 空间复杂度:
- 递归:O(h) 递归栈
- 显式栈/颜色标记:O(h) 栈空间
- Morris:O(1) 额外空间(会临时建立和拆除线索)
关键边界与陷阱
- 空树:返回空切片。
- 只有一个节点:直接返回该值。
- 极度不平衡树(链状):递归可能接近最坏栈深;Morris优势明显。
- Morris一定要"复原"pre.Right,否则破坏原树结构。
方法与要点
- 递归:模板清晰,先左后根再右。
- 栈迭代:while(cur!=nil)先一路向左入栈;否则出栈访问,再转向右。
- 颜色标记:节点入栈两次,第一次展开(入右、入自身白、入左),第二次(黑)访问。
- Morris:寻找左子树最右节点pre;首遇建立线索,二遇拆线索并访问根。
测试用例设计
-
1,null,2,3\] -\> \[1,3,2
-
\] -\> \[
-
1\] -\> \[1
- 完全二叉树如 [4,2,6,1,3,5,7] -> [1,2,3,4,5,6,7]
- 退化链(全左/全右)
实战技巧
- Morris 空间O(1)是亮点,注意指针复原顺序。
- 颜色标记可统一前/中/后序实现,便于模板化。
- 递归实现最易读,作为基线版本很有用。
完整题解代码
go
package main
import (
"fmt"
)
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
// =========================== 方法一:递归 ===========================
func inorderTraversal1(root *TreeNode) []int {
var res []int
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
if node == nil {
return
}
dfs(node.Left)
res = append(res, node.Val)
dfs(node.Right)
}
dfs(root)
return res
}
// =========================== 方法二:显式栈迭代 ===========================
func inorderTraversal2(root *TreeNode) []int {
var res []int
stack := []*TreeNode{}
cur := root
for cur != nil || len(stack) > 0 {
for cur != nil {
stack = append(stack, cur)
cur = cur.Left
}
n := len(stack) - 1
node := stack[n]
stack = stack[:n]
res = append(res, node.Val)
cur = node.Right
}
return res
}
// =========================== 方法三:颜色标记统一迭代 ===========================
// 白色:0 表示展开;黑色:1 表示访问
func inorderTraversal3(root *TreeNode) []int {
if root == nil {
return nil
}
type item struct {
node *TreeNode
clr int
}
const white, black = 0, 1
var res []int
stack := []item{{root, white}}
for len(stack) > 0 {
n := len(stack) - 1
i := stack[n]
stack = stack[:n]
if i.node == nil {
continue
}
if i.clr == white {
// 中序:右 黑 自 黑 左 白
stack = append(stack, item{i.node.Right, white})
stack = append(stack, item{i.node, black})
stack = append(stack, item{i.node.Left, white})
} else {
res = append(res, i.node.Val)
}
}
return res
}
// =========================== 方法四:Morris 遍历 ===========================
func inorderTraversal4(root *TreeNode) []int {
var res []int
cur := root
for cur != nil {
if cur.Left == nil {
res = append(res, cur.Val)
cur = cur.Right
continue
}
// 寻找前驱(左子树最右)
pre := cur.Left
for pre.Right != nil && pre.Right != cur {
pre = pre.Right
}
if pre.Right == nil {
pre.Right = cur
cur = cur.Left
} else {
pre.Right = nil
res = append(res, cur.Val)
cur = cur.Right
}
}
return res
}
// =========================== 构建/工具 ===========================
func arrayToTreeLevelOrder(arr []any) *TreeNode {
// 基于层序队列的构建:按顺序为每个出队节点填充左/右孩子
if len(arr) == 0 {
return nil
}
if arr[0] == nil {
return nil
}
root := &TreeNode{Val: arr[0].(int)}
queue := []*TreeNode{root}
i := 1
for i < len(arr) && len(queue) > 0 {
n := queue[0]
queue = queue[1:]
// 左孩子
if i < len(arr) {
if arr[i] != nil {
left := &TreeNode{Val: arr[i].(int)}
n.Left = left
queue = append(queue, left)
}
i++
}
// 右孩子
if i < len(arr) {
if arr[i] != nil {
right := &TreeNode{Val: arr[i].(int)}
n.Right = right
queue = append(queue, right)
}
i++
}
}
return root
}
func equalSlice(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func buildRightChain(vals []int) *TreeNode {
if len(vals) == 0 {
return nil
}
root := &TreeNode{Val: vals[0]}
cur := root
for i := 1; i < len(vals); i++ {
cur.Right = &TreeNode{Val: vals[i]}
cur = cur.Right
}
return root
}
// =========================== 测试 ===========================
func main() {
fmt.Println("=== LeetCode 94: 二叉树的中序遍历 ===\n")
testCases := []struct {
name string
root *TreeNode
expected []int
}{
{
name: "例1: [1,null,2,3]",
root: arrayToTreeLevelOrder([]any{1, nil, 2, 3}),
expected: []int{1, 3, 2},
},
{
name: "空树",
root: arrayToTreeLevelOrder([]any{}),
expected: []int{},
},
{
name: "单节点",
root: arrayToTreeLevelOrder([]any{1}),
expected: []int{1},
},
{
name: "完全二叉树",
root: arrayToTreeLevelOrder([]any{4, 2, 6, 1, 3, 5, 7}),
expected: []int{1, 2, 3, 4, 5, 6, 7},
},
{
name: "全左链",
root: arrayToTreeLevelOrder([]any{3, 2, nil, 1}),
expected: []int{1, 2, 3},
},
{
name: "全右链",
root: buildRightChain([]int{1, 2, 3}),
expected: []int{1, 2, 3},
},
}
methods := map[string]func(*TreeNode) []int{
"递归": inorderTraversal1,
"栈迭代": inorderTraversal2,
"颜色标记": inorderTraversal3,
"Morris O(1)": inorderTraversal4,
}
for name, f := range methods {
fmt.Printf("方法:%s\n", name)
pass := 0
for i, tc := range testCases {
got := f(tc.root)
ok := equalSlice(got, tc.expected)
status := "✅"
if !ok {
status = "❌"
}
fmt.Printf(" 测试%d(%s): %s\n", i+1, tc.name, status)
if !ok {
fmt.Printf(" 输出: %v\n 期望: %v\n", got, tc.expected)
} else {
pass++
}
}
fmt.Printf(" 通过: %d/%d\n\n", pass, len(testCases))
}
}