【LeetHOT100】二叉树的中序遍历——Java多解法详解

一、题目描述

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 核心思路

递归是最直观的实现方式:

  1. 先遍历左子树

  2. 访问根节点

  3. 再遍历右子树

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. 从根节点开始,将所有左子节点压入栈

  2. 弹出栈顶节点,访问它

  3. 将指针移动到栈顶节点的右子节点,重复步骤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) 空间的中序遍历。

核心思想

  1. 建立临时链接:找到当前节点的前驱节点(左子树的最右节点),将其右指针指向当前节点

  2. 遍历左子树

  3. 访问当前节点

  4. 恢复原树结构(断开临时链接)

  5. 遍历右子树

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 常见错误

  1. 递归解法

    • 忘记处理 null 边界条件

    • add 的顺序写错(前序/后序混淆)

  2. 迭代解法

    • 忘记将左子节点全部入栈

    • 访问节点后忘记将 cur 指向右子节点

    • 栈中元素处理顺序错误

  3. 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;
}

八、相关链接

相关推荐
_日拱一卒4 分钟前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
隔窗听雨眠11 分钟前
Nginx网关响应慢排查手记
java·服务器·nginx
智慧物业老杨35 分钟前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
源码宝1 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区1 小时前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
弥树子1 小时前
踩坑记录:服务器内网调用接口,真实请求URL与官方公开URL不一致问题排查
开发语言·php
金銀銅鐵1 小时前
[Java] 如何理解 class 文件中方法的 descriptor?
java·后端
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【63】AI Agent 长期记忆
java·人工智能·spring
z落落2 小时前
C# ToCharArray + foreach遍历 + String与StringBuilder
开发语言·c#
憧憬成为java架构高手的小白2 小时前
苍穹外卖--day09
java·spring boot·百度