LeetCode 94. 二叉树的中序遍历
题目描述

题意分析
给定一棵二叉树的根节点,要求按「中序」返回所有节点值组成的列表。中序的定义是固定的:先访问左子树、再访问根节点、最后访问右子树,即「左 → 根 → 右」。
约束信号很温和:节点数最多 100,值域也很小,任何线性做法都绰绰有余。真正的考点写在进阶里——递归解法很平凡,题目明确要求你给出迭代版本,这才是面试官想看的部分。
边界上要注意:空树应返回空列表而不是
null;单节点树返回只含根值的列表;退化成一条链的树也必须能正确处理,它会把辅助空间推到最坏情况。
解法:栈模拟递归中序遍历
核心思路
中序遍历的顺序是“左子树 → 根节点 → 右子树”。使用栈保存尚未访问的祖先节点:先不断沿左孩子入栈,走到空节点后弹出栈顶并记录,再转向它的右子树。
cur表示当前要处理的节点,栈表示左子树尚未处理完的祖先链。
解题步骤
- 初始化结果列表、空栈和
cur = root。- 将
cur及其左链依次入栈。- 左侧走到尽头后,弹出栈顶并记录节点值。
- 将
cur指向该节点的右孩子,重复以上过程,直到cur为空且栈也为空。
代码实现
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
ans.add(cur.val);
cur = cur.right;
}
return ans;
}
}
func inorderTraversal(root *TreeNode) []int {
ans := []int{}
stack := []*TreeNode{}
cur := root
for cur != nil || len(stack) > 0 {
for cur != nil {
stack = append(stack, cur)
cur = cur.Left
}
cur = stack[len(stack)-1]
stack = stack[:len(stack)-1]
ans = append(ans, cur.Val)
cur = cur.Right
}
return ans
}
复杂度分析
- 时间复杂度:$O(n)$,每个节点入栈、出栈各一次。
- 空间复杂度:$O(h)$,
h为树高;最坏为 $O(n)$。
关键点总结
- 节点入栈时不访问,出栈时才记录其值。
- 外层循环必须同时检查
cur和栈,二者都为空才结束。- 转向右子树后,会用相同流程处理其左链。
易错点总结
- 只以
cur != null作为循环条件,会漏掉栈中尚未访问的节点。- 入栈时记录节点值,会写成前序遍历。
- 弹栈后忘记转向右孩子,会重复访问或漏掉右子树。
- Go 弹栈时只读取栈顶却不缩短切片,会导致死循环。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 144. 二叉树的前序遍历 | 简单 | 同一栈框架,访问时机提前到入栈时 |
| 145. 二叉树的后序遍历 | 简单 | 需记录右子树是否访问过,三种遍历中迭代最难 |
| 589. N 叉树的前序遍历 | 简单 | 推广到多叉:孩子需逆序入栈保证顺序 |
| 590. N 叉树的后序遍历 | 简单 | 多叉后序等于「根右到左的前序」再整体反转 |
| 173. 二叉搜索树迭代器 | 中等 | 把中序迭代拆成 next/hasNext,栈状态跨调用保存 |
| 230. 二叉搜索树中第 K 小的元素 | 中等 | 利用 BST 中序有序性,遍历到第 $k$ 个即提前终止 |