Vvvrf / DSA.log← 返回全部笔记
LEETCODE 094Easy

二叉树的中序遍历

递归天然记住了「回来以后接着往下走」,显式栈就得自己把这件事补上。

问题拆解

中序遍历的规则是:先访问左子树,再访问根,最后访问右子树。递归写法几乎不用动脑,但题目进阶要求用迭代完成,这才是值得琢磨的地方。

难点在于:递归之所以简单,是因为函数调用栈帮我们记住了「处理完左子树后,还得回到当前节点、再去处理右子树」。改成迭代,就得自己用一个栈把这条「回头路」显式管理起来。

迭代版遍历的本质,是手动复刻递归调用栈干的活。

递归写法

def inorderTraversal(root):
    result = []

    def visit(node):
        if not node:
            return
        visit(node.left)
        result.append(node.val)
        visit(node.right)

    visit(root)
    return result

三行顺序就是中序定义本身,改一改顺序就能得到前序或后序。

显式栈迭代

用一个指针 cur 一路向左把节点压栈,走到底后弹出访问,再转向它的右子树:

def inorderTraversal(root):
    result, stack = [], []
    cur = root

    while cur or stack:
        while cur:
            stack.append(cur)
            cur = cur.left
        cur = stack.pop()
        result.append(cur.val)
        cur = cur.right

    return result

内层循环负责「尽量往左」,弹栈时机就对应递归里「左子树处理完了」的那一刻,访问完当前值后把 cur 指向右孩子,循环会自动对右子树重复同样的过程。

复杂度

指标 复杂度 原因
时间 O(n) 每个节点入栈、出栈各一次
空间 O(h) 栈里最多存一条从根到当前节点的路径

可以迁移的模式

  • 任何递归都能用显式栈改写成迭代,关键是想清楚「栈里存的是待返回的现场」;
  • 「一路向某个方向走到底,再回退」的结构,天然对应压栈—弹栈;
  • 遍历顺序的差异只在访问根节点的时机,框架可以复用。

当递归深度可能失控、或面试要求手写栈时,这套「模拟调用栈」的思路就派上用场了。