LeetCode 94. 二叉树的中序遍历(Inorde

一、题目描述

给定一个二叉树的 根节点 root ,返回它的 中序遍历​ 结果。

中序遍历顺序:

左子树 → 根节点 → 右子树

示例:

复制代码
输入:root = [1,null,2,3]
输出:[1,3,2]

输入:root = []
输出:[]

输入:root = [1]
输出:[1]

二、为什么这题"看起来简单但值得深究"

如果你熟悉递归,这题一句话就能写完:

先遍历左子树,再访问根,再遍历右子树。

但问题是:

  • 递归写法真的理解了吗?

  • 能不能用 O(1) 额外空间(不算递归栈)完成?

  • 面试官会不会让你写迭代版?

👉 所以我们必须从 递归 → 迭代 → Morris 遍历​ 逐层推进。


三、核心思想:中序遍历的本质

1️⃣ 递归视角(最直观)

中序遍历的规则是固定的:

复制代码
inorder(root):
    inorder(left)
    visit(root)
    inorder(right)

递归之所以成立,是因为:

  • 二叉树天然具有 子问题结构

  • 每一棵子树都满足同样的遍历规则


2️⃣ 迭代视角(显式栈)

递归本质上使用的是 系统调用栈

如果我们手动维护一个栈:

  • 一路向左走到底

  • 弹栈访问节点

  • 转向右子树

👉 这就是中序遍历的迭代写法。


3️⃣ Morris 遍历(O(1) 额外空间)

Morris 遍历的核心思想是:

利用二叉树中原本为 null 的指针,临时建立"线索",遍历完成后再恢复结构。

它不需要栈,也不修改节点 val,只调整指针。


四、解法一:递归(最推荐,面试首选)

思路

  • 左子树

  • 右子树

Java 实现

复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    private void inorder(TreeNode node, List<Integer> res) {
        if (node == null) return;
        inorder(node.left, res);
        res.add(node.val);
        inorder(node.right, res);
    }
}

复杂度分析

指标 数值
时间复杂度 O(n)
空间复杂度 O(h)(递归栈,h 为树高)

面试时,90% 情况写到这就够了。


五、解法二:迭代(显式栈)

核心流程

  1. 当前节点一直向左走,沿途入栈

  2. 弹栈,访问节点

  3. 转向右子树,重复步骤 1

Java 实现

复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = 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();
            res.add(cur.val);
            cur = cur.right;
        }
        return res;
    }
}

复杂度分析

指标 数值
时间复杂度 O(n)
空间复杂度 O(h)

适合面试官追问"不用递归怎么写"的场景。


六、解法三:Morris 中序遍历(O(1) 空间)

核心思想

  • 如果左子树为空:访问当前节点,走向右子树

  • 如果左子树不为空:

    • 找到 左子树中最右的节点(前驱)

    • 将其 right指向当前节点

    • 当前节点移向左子树

    • 当第二次回到当前节点时,恢复指针并访问

Java 实现

复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        TreeNode cur = root;

        while (cur != null) {
            if (cur.left == null) {
                res.add(cur.val);
                cur = cur.right;
            } else {
                TreeNode pre = cur.left;
                while (pre.right != null && pre.right != cur) {
                    pre = pre.right;
                }

                if (pre.right == null) {
                    pre.right = cur;
                    cur = cur.left;
                } else {
                    pre.right = null;
                    res.add(cur.val);
                    cur = cur.right;
                }
            }
        }
        return res;
    }
}

复杂度分析

指标 数值
时间复杂度 O(n)
空间复杂度 O(1) ✅

这是真正的"不借助栈、O(1) 空间"解法。


七、三种解法对比总结

解法 是否推荐 空间复杂度 面试评价
递归 ⭐⭐⭐⭐⭐ O(h) 最常用,最安全
迭代 ⭐⭐⭐⭐ O(h) 展示基本功
Morris ⭐⭐⭐ O(1) 高级加分项

八、面试高频追问

❓ **为什么中序遍历是有序的?**​

✅ 因为 BST 的中序遍历天然递增。

❓ **递归和迭代哪个更好?**​

✅ 面试先写递归,再补迭代。

❓ **Morris 遍历会破坏树结构吗?**​

✅ 不会,指针会被恢复。


九、一句话总结

递归写左中右,迭代显式用栈,Morris 借指针,中序遍历不再难。