一、题目描述
94. 二叉树的中序遍历
给定一个二叉树的根节点 root,返回它的 中序遍历。
中序遍历 的定义:按照 左子树 → 根节点 → 右子树 的顺序遍历二叉树。
示例 1:
输入:
root = [1,null,2,3]输出:
[1,3,2]
示例 2:
输入:
root = []输出:
[]
示例 3:
输入:
root = [1]输出:
[1]
提示:
-
树中节点数目在范围
[0, 100]内 -
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
二、解题思路概览
二叉树的中序遍历是树的三种基本遍历(前序、中序、后序)之一。常见解法有三种:
| 解法 | 时间复杂度 | 空间复杂度 | 特点 | 面试推荐 |
|---|---|---|---|---|
| 递归 | O(n) | O(n) (最坏) | 代码简洁,思路清晰 | ⭐⭐⭐⭐⭐ |
| 迭代(栈) | O(n) | O(n) | 模拟递归,面试常考 | ⭐⭐⭐⭐⭐ |
| Morris 遍历 | O(n) | O(1) | 空间最优,实现稍复杂 | ⭐⭐⭐ |
三、解法一:递归(最简洁)
3.1 核心思路
递归是最直观的实现方式:
-
先遍历左子树
-
访问根节点
-
再遍历右子树
3.2 代码实现
java
class Solution {
private List<Integer> result = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
dfs(root);
return result;
}
private void dfs(TreeNode node) {
if (node == null) {
return;
}
dfs(node.left); // 1. 遍历左子树
result.add(node.val); // 2. 访问根节点
dfs(node.right); // 3. 遍历右子树
}
}
3.3 图解示例
以 root = [1,null,2,3] 为例(树的结构):
text
1
\
2
/
3
递归过程:
text
dfs(1):
→ dfs(1.left=null): 返回
→ 结果添加 1
→ dfs(1.right=2):
→ dfs(2.left=3):
→ dfs(3.left=null): 返回
→ 结果添加 3
→ dfs(3.right=null): 返回
→ 结果添加 2
→ dfs(2.right=null): 返回
→ 返回
结果顺序: [1, 3, 2]
3.4 复杂度分析
-
时间复杂度:O(n),每个节点恰好被访问一次
-
空间复杂度:O(n),最坏情况下(单链表树)递归栈深度为 n
四、解法二:迭代(显式栈)⭐⭐⭐⭐⭐
4.1 核心思路
用栈模拟递归调用过程:
-
从根节点开始,将所有左子节点压入栈
-
弹出栈顶节点,访问它
-
将指针移动到栈顶节点的右子节点,重复步骤1
4.2 代码实现
java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
// 1. 不断向左走,将所有左子节点压入栈
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
// 2. 弹出节点并访问
cur = stack.pop();
result.add(cur.val);
// 3. 转向右子树
cur = cur.right;
}
return result;
}
}
4.3 图解示例
以 root = [1,null,2,3] 为例:
text
步骤1: cur=1, stack=[1]
步骤2: cur=1.left=null,退出内层循环
步骤3: 弹出1,访问1 → result=[1], cur=1.right=2
步骤4: cur=2, while循环: stack=[2]
步骤5: cur=2.left=3, stack=[2,3]
步骤6: cur=3.left=null,退出内层循环
步骤7: 弹出3,访问3 → result=[1,3], cur=3.right=null
步骤8: cur=null, stack=[2]
步骤9: 弹出2,访问2 → result=[1,3,2], cur=2.right=null
步骤10: cur=null, stack为空,结束
4.4 复杂度分析
-
时间复杂度:O(n),每个节点入栈出栈各一次
-
空间复杂度:O(n),栈最多存储树的高度个节点
五、解法三:Morris 遍历(O(1) 空间)
5.1 核心思路
Morris 遍历利用树的空闲指针(叶节点的左右空指针)来实现 O(1) 空间的中序遍历。
核心思想:
-
建立临时链接:找到当前节点的前驱节点(左子树的最右节点),将其右指针指向当前节点
-
遍历左子树
-
访问当前节点
-
恢复原树结构(断开临时链接)
-
遍历右子树
5.2 代码实现
java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
// 没有左子树,直接访问当前节点
result.add(cur.val);
cur = cur.right; // 转向右子树
} else {
// 找到左子树的最右节点(前驱节点)
TreeNode predecessor = cur.left;
while (predecessor.right != null && predecessor.right != cur) {
predecessor = predecessor.right;
}
if (predecessor.right == null) {
// 建立临时链接
predecessor.right = cur;
cur = cur.left; // 继续遍历左子树
} else {
// 临时链接已存在,说明左子树已遍历完
predecessor.right = null; // 断开临时链接
result.add(cur.val); // 访问当前节点
cur = cur.right; // 转向右子树
}
}
}
return result;
}
}
5.3 图解示例
以 root = [1,2,3,4,5] 为例:
text
原始树:
1
/ \
2 3
/ \
4 5
步骤1:cur=1, 找到前驱=5(2的右子树最右)
将5.right指向1
cur → 2
步骤2:cur=2, 找到前驱=4
将4.right指向2
cur → 4
步骤3:cur=4, 左子树为空
访问4
cur → 4.right(指向2)
步骤4:cur=2, 前驱的right已指向2
断开4.right
访问2
cur → 2.right=5
步骤5:cur=5, 左子树为空
访问5
cur → 5.right(指向1)
步骤6:cur=1, 前驱的right已指向1
断开5.right
访问1
cur → 1.right=3
...后续遍历右子树
5.4 复杂度分析
-
时间复杂度:O(n),每个节点最多被访问两次
-
空间复杂度:O(1),只使用了常数个指针
5.5 Morris 遍历的特点
| 优点 | 缺点 |
|---|---|
| 空间复杂度 O(1) | 实现较复杂,容易出错 |
| 不占用额外栈空间 | 会临时修改树的结构(但最终会恢复) |
| 适合对空间敏感的嵌入式场景 | 理解难度较高 |
六、解法对比与总结
| 方法 | 时间复杂度 | 空间复杂度 | 代码复杂度 | 面试推荐 |
|---|---|---|---|---|
| 递归 | O(n) | O(n) | 极简 | ⭐⭐⭐⭐⭐ |
| 迭代(栈) | O(n) | O(n) | 中等 | ⭐⭐⭐⭐⭐ |
| Morris | O(n) | O(1) | 较复杂 | ⭐⭐⭐ |
七、面试建议
7.1 应该用哪个?
| 场景 | 推荐解法 |
|---|---|
| 面试标准答案 | 递归 或 迭代(栈) |
| 被要求写迭代 | 显式栈版本 |
| 被问是否能 O(1) 空间 | Morris 遍历 |
| 快速实现 | 递归 |
7.2 常见错误
-
递归解法:
-
忘记处理
null边界条件 -
将
add的顺序写错(前序/后序混淆)
-
-
迭代解法:
-
忘记将左子节点全部入栈
-
访问节点后忘记将 cur 指向右子节点
-
栈中元素处理顺序错误
-
-
Morris 遍历:
-
循环条件写错
-
忘记恢复前驱节点的
right指针 -
前驱查找时没有判断
predecessor.right != cur
-
7.3 三种遍历的统一模板
如果你需要同时掌握前序、中序、后序的迭代写法,可以参考这个模式:
java
// 中序遍历的迭代模板
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left; // 中序:先左
} else {
cur = stack.pop();
result.add(cur.val); // 中序:再中
cur = cur.right; // 中序:后右
}
}
return result;
}
八、相关链接
-
官方题解 :二叉树的中序遍历官方题解
-
LeetCode Hot 100 汇总:力扣热题100题