144. 二叉树的前序遍历(Binary Tree Preorder Traversal)

难度:简单 | 主题:二叉树、DFS、栈、Morris遍历

题目

给你二叉树的根节点 root,返回它节点值的前序遍历。

示例

 
输入:root = [1,null,2,3]
 
输出:[1,2,3]
 

思路

先讲个故事:公司层层汇报

你是 CEO,要听全公司的汇报。规矩是:每个部门经理先汇报自己,再让左团队汇报,最后让右团队汇报。

这就是前序遍历——根 → 左 → 右。

从 CEO(根)开始:

  1. CEO 先讲

  2. 左部门经理讲 → 左部门的下属依次

  3. 右部门经理讲 → 右部门的下属依次


引导式推导:从递归到迭代

第 1 层:递归(最直观)

 
def preorder(root):
 
    if not root: return
 
    print(root.val)           # 先处理根
 
    preorder(root.left)       # 再遍历左
 
    preorder(root.right)      # 最后遍历右
 

三行代码,和文字描述一一对应。但递归深度 = 树高,树特别深时会栈溢出。

第 2 层:迭代(用栈模拟递归)

递归底层就是系统调用栈。我们自己维护一个栈,手动控制入栈出栈。

关键技巧:栈是 LIFO(后进先出),所以入栈时要先右后左——这样左孩子就会在栈顶,先被弹出。


graph TD

    subgraph 迭代过程

        S1["初始栈: [1]"] -->|"pop 1→访问"| S2["栈: [3,2]<br/>(先推右3,再推左2)"]

        S2 -->|"pop 2→访问"| S3["栈: [3]<br/>(2无子节点)"]

        S3 -->|"pop 3→访问"| S4["栈: []<br/>(3无子节点)"]

    end

    style S1 fill:#e1f5fe

    style S2 fill:#e1f5fe

    style S3 fill:#e1f5fe

    style S4 fill:#e1f5fe

第 3 层:Morris 遍历(O(1) 空间)

利用树的大量空闲右指针做线索,遍历完恢复原状。了解即可,不常考。

解法时间空间推荐度
迭代(推荐)O(n)O(h)⭐⭐⭐⭐⭐
递归O(n)O(h)⭐⭐⭐⭐
MorrisO(n)O(1)⭐⭐

代码

 
# 迭代(推荐写法)
 
def preorderTraversal(self, root):      # 主函数:前序遍历 根-左-右
 
    if not root:
 
        return []
 
    result = []
 
    stack = [root]
 
    while stack:
 
        node = stack.pop()
 
        result.append(node.val)         # 前序:先访问根
 
        if node.right:
 
            stack.append(node.right)    # 右先入栈
 
        if node.left:
 
            stack.append(node.left)     # 左后入栈 → 左先出
 
    return result
 
# 递归(最直观)
 
def preorderTraversal(self, root):
 
    result = []
 
    def dfs(node):
 
        if not node:
 
            return
 
        result.append(node.val)         # 根
 
        dfs(node.left)                  # 左
 
        dfs(node.right)                 # 右
 
    dfs(root)
 
    return result
 
# Morris 遍历(O(1)空间,了解即可)
 
def preorderTraversal(self, root):
 
    result = []
 
    curr = root
 
    while curr:
 
        if curr.left:
 
            predecessor = curr.left
 
            while predecessor.right and predecessor.right != curr:
 
                predecessor = predecessor.right
 
            if not predecessor.right:
 
                result.append(curr.val)
 
                predecessor.right = curr
 
                curr = curr.left
 
            else:
 
                predecessor.right = None
 
                curr = curr.right
 
        else:
 
            result.append(curr.val)
 
            curr = curr.right
 
    return result
 

复杂度

指标解释
时间O(n)每个节点访问一次
空间(迭代/递归)O(h)栈深度 = 树高
空间(Morris)O(1)利用空闲指针,不额外开栈

关键点

  • 迭代栈:右孩子先入、左孩子后入,保证左先出

  • Morris 遍历的核心是找「前驱节点」(左子树最右节点),通过临时右指针线索化

  • 递归最简洁,但考迭代是为了考察你对递归底层实现(栈)的理解

  • 三种 DFS 遍历的迭代写法中,前序最简单


实战考量

频率分析

出现在:几乎所有 AI Agent 的二叉树开场题。这道题先确认写过树的遍历,再进入更复杂的题。迭代版是重点考核点。

延伸思考

Q:递归和迭代的本质区别是什么?

A:递归用系统调用栈(隐式),迭代用显式栈。本质一模一样——都是 DFS,只是栈的维护方式不同。

Q:Morris 遍历为什么能做到 O(1) 空间?

A:利用树的空闲右指针做线索,遍历完再恢复原状。不额外开栈,所以 O(1)。代价是遍历过程中会临时修改树的结构。

Q:如果树很深,递归会栈溢出,怎么办?

A:改用迭代(显式栈),或增加系统栈深度限制。Python 默认递归深度 ~1000。

Q:前序、中序、后序的迭代写法有什么区别?

A:前序最简(出栈即访问);中序需要一直往左走到头再访问;后序可以用前序变形 + 反转来取巧。

易错点

  • 迭代时右孩子先入栈,不是左孩子先入

  • Morris 遍历时 predecessor.right != curr 防止死循环

  • 递归终止条件不要漏掉空节点


生活类比

前序遍历 → 公司汇报 → 迭代栈

CEO(根节点)先讲自己的事儿,再让左团队讲、最后让右团队讲。

用栈模拟就像在看板上钉便签:先钉右团队的(钉下面),再钉左团队的(钉上面),

这样每次揭便签时左团队先被揭——先左后右。


相关题目

题目关系
94二叉树的中序遍历DFS 遍历同族,左-根-右
145二叉树的后序遍历DFS 遍历同族,左-右-根
102二叉树的层序遍历BFS 层序,按层遍历

→ 返回题单:LeetCode学习路线图 > 五、二叉树

速记卡(面试闪卡)

Q1:一句话讲清「144. 二叉树的前序遍历(Binary Tree Preorder Traversal)」到底是什么?

A:按根→左→右的顺序输出二叉树所有节点的值。

Q2:题目 —— 怎么理解?

A:像 CEO 汇报:给你二叉树根节点,返回节点值的前序遍历(根先、再左、再右)。示例 root=[1,null,2,3] 输出 [1,2,3]。

Q3:思路 —— 怎么理解?

A:像层层汇报:CEO(根)先讲,再让左团队讲、最后右团队讲,就是根→左→右。递归三行最直白;迭代用栈模拟——栈是 LIFO,所以入栈先右后左,保证左先弹出。还有 Morris 遍历借空闲右指针做到 O(1) 空间,了解即可。

Q4:代码 —— 怎么理解?

A:迭代:栈压 root,循环 pop 出来先访问,再压右、压左(左后压→先出)。递归:访问根→dfs 左→dfs 右。Morris:找前驱节点(左子树最右)临时右指针线索化,访问完复原,O(1) 空间但会临时改树。

Q5:复杂度 —— 怎么理解?

A:像每个节点看一眼:时间 O(n) 每个节点访问一次;空间迭代/递归 O(h)(h 为树高,栈深=树高),Morris O(1) 借指针不另开栈。树太深递归会爆栈,改用迭代。

Q6:核心速记主线有哪些?

  • 前序顺序:根 → 左 → 右

  • 迭代栈关键:右孩子先入、左孩子后入,保证左先出

  • 递归最简洁但考迭代是看你对系统栈的理解

  • 三种 DFS 里前序迭代最简单;深度大用迭代防栈溢出

口诀

A:前序根左右,

迭代栈记牢;

右先左后入,

左出先报到。

相关链接