二叉树是算法面试中的高频考点,从基础的遍历到复杂的构造问题,掌握二叉树的处理技巧是算法能力的重要体现。本文系统梳理二叉树的四种遍历方式(前序、中序、后序、层序),深入探讨递归与迭代两种实现方法,并通过经典构造问题(从前序中序构造、从后序中序构造)帮助读者建立完整的二叉树解题框架。我们还将分析复杂度、常见错误、优化技巧和变体扩展。
系列导航
📚 LeetCode 算法精讲系列 (共 10 篇):
哈希表(两数之和、最长连续序列、字母异位词分组)
双指针技巧(对撞指针、快慢指针、滑动窗口)
链表操作(反转、环检测、合并)
滑动窗口技巧
二分查找
→ 二叉树遍历与构造 (前中后序、层序、构造)←
当前文章
动态规划入门(一维/二维 DP 、状态转移)
回溯算法(排列、组合、剪枝)
贪心算法(区间调度、跳跃游戏)
栈与队列(括号匹配、单调栈)
二叉树基础回顾
什么是二叉树?
二叉树( Binary
Tree)是每个节点最多有两个子节点的树结构。通常子节点被称为"左子节点"和"右子节点"。
核心特性 :
每个节点最多有两个子节点
左子树和右子树是有序的 (不能随意交换)
空树也是二叉树
基本结构 :
1 2 3 4 5 class TreeNode : def __init__ (self, val=0 , left=None , right=None ): self.val = val self.left = left self.right = right
二叉树的分类
完全二叉树 :除了最后一层,其他层节点都填满,最后一层从左到右填充。
满二叉树 :所有层都完全填满的二叉树。
二叉搜索树( BST) :对于每个节点,左子树所有节点值
< 节点值 < 右子树所有节点值。
平衡二叉树 :左右子树高度差不超过 1 。
LeetCode 144:
二叉树的前序遍历
问题描述
给定一个二叉树的根节点
root,返回它的节点值的前序遍历 。
前序遍历顺序 :根节点 → 左子树 → 右子树
示例 :
输入:root = [1,null,2,3]
输出:[1,2,3]
约束条件 :
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
问题分析
前序遍历的核心是先访问根节点,再递归处理左子树,最后递归处理右子树 。这是最直观的遍历方式,因为总是先看到根节点的值。
应用场景 :
打印目录结构(先打印当前目录,再打印子目录)
复制二叉树(先创建根节点,再复制子树)
表达式树求值(先处理运算符,再处理操作数)
解题思路
递归思路 :直接按照定义实现——先访问根,再递归左子树,最后递归右子树。
迭代思路 :使用栈模拟递归过程。将根节点入栈,然后循环:弹出栈顶节点并访问,先压入右子节点,再压入左子节点(注意顺序,因为栈是后进先出)。
复杂度分析
时间复杂度 : ,需要访问每个节点一次
空间复杂度 :
递归: ,
为树的高度(递归栈深度)
迭代: ,栈的最大深度为树的高度
递归实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from typing import List , Optional class Solution : def preorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 前序遍历:根 -> 左 -> 右 算法步骤: 1. 如果根节点为空,返回空列表 2. 访问根节点(添加到结果) 3. 递归遍历左子树 4. 递归遍历右子树 边界条件: - 空树:直接返回 [] - 单节点树:返回 [val] 变量含义: - root: 当前子树的根节点 - result: 存储遍历结果的列表 """ result = [] def preorder (node ): if not node: return result.append(node.val) preorder(node.left) preorder(node.right) preorder(root) return result
算法原理 :
前序遍历遵循"根-左-右"的顺序。递归实现利用了函数调用栈,当访问完左子树后,会自动返回到上一层继续处理右子树。
执行过程示例 (树
[1, null, 2, 3]):
1 2 3 4 5 6 7 8 9 访问节点 1 -> result = [1] 递归左子树( null)-> 直接返回 递归右子树(节点 2) 访问节点 2 -> result = [1, 2] 递归左子树(节点 3) 访问节点 3 -> result = [1, 2, 3] 递归左子树( null) 递归右子树( null) 递归右子树( null)
迭代实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 def preorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 迭代实现:使用栈模拟递归 算法步骤: 1. 初始化栈,将根节点入栈 2. 当栈不为空时循环: a. 弹出栈顶节点并访问 b. 如果右子节点存在,入栈 c. 如果左子节点存在,入栈 3. 返回结果 关键技巧: - 先压右子节点,再压左子节点(因为栈是 LIFO) - 这样弹出时就是"根-左-右"的顺序 边界条件: - 空树:栈为空,直接返回 [] """ 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
为什么先压右后压左 :
栈是后进先出(
LIFO)的数据结构。如果我们希望先访问左子树,就需要让左子节点后入栈,这样它就会先出栈。
执行过程示例 (树
[1, null, 2, 3]):
1 2 3 4 5 6 7 8 9 10 11 初始: stack = [1], result = [] 迭代 1:弹出 1 -> result = [1] 压入 2(右)-> stack = [2] 压入 null(左,跳过) 迭代 2:弹出 2 -> result = [1, 2] 压入 null(右,跳过) 压入 3(左)-> stack = [3] 迭代 3:弹出 3 -> result = [1, 2, 3] 压入 null(右,跳过) 压入 null(左,跳过) 迭代 4: stack 为空,结束
算法过程可视化 :下面的动画演示了前序遍历如何按"根-左-右"顺序访问节点:
常见错误
错误一:忘记处理空节点
1 2 3 4 5 6 7 8 9 10 11 12 13 def preorder (node ): result.append(node.val) preorder(node.left) preorder(node.right) def preorder (node ): if not node: return result.append(node.val) preorder(node.left) preorder(node.right)
错误二:迭代实现中压栈顺序错误
1 2 3 4 5 6 7 8 9 10 11 12 if node.left: stack.append(node.left) if node.right: stack.append(node.right) if node.right: stack.append(node.right) if node.left: stack.append(node.left)
变体扩展
变体一: Morris
前序遍历(空间复杂度 )
Morris 遍历利用树中的空指针,实现 空间复杂度的遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def preorderTraversal_morris (self, root: Optional [TreeNode] ) -> List [int ]: """ Morris 前序遍历: O(1)空间复杂度 核心思想:利用叶子节点的空指针,建立临时链接 当访问完左子树后,通过这个链接回到根节点 """ result = [] current = root while current: if not current.left: result.append(current.val) current = current.right else : predecessor = current.left while predecessor.right and predecessor.right != current: predecessor = predecessor.right if not predecessor.right: result.append(current.val) predecessor.right = current current = current.left else : predecessor.right = None current = current.right return result
复杂度分析 :
时间复杂度 : ,每个节点最多访问两次
空间复杂度 : ,只使用常数额外空间
LeetCode 94: 二叉树的中序遍历
问题描述
给定一个二叉树的根节点
root,返回它的节点值的中序遍历 。
中序遍历顺序 :左子树 → 根节点 → 右子树
示例 :
输入:root = [1,null,2,3]
输出:[1,3,2]
问题分析
中序遍历的特点是先处理左子树,再访问根节点,最后处理右子树 。对于二叉搜索树(
BST),中序遍历的结果是有序的。
应用场景 :
BST 有序输出 :中序遍历 BST 得到升序序列
表达式树求值 :中序遍历表达式树得到中缀表达式
验证 BST :检查中序遍历结果是否有序
解题思路
递归思路 :先递归左子树,再访问根节点,最后递归右子树。
迭代思路 :使用栈模拟递归。从根节点开始,一直向左走到底,将路径上的节点都入栈。然后弹出栈顶节点并访问,转向右子树,重复上述过程。
复杂度分析
递归实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def inorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 中序遍历:左 -> 根 -> 右 算法步骤: 1. 递归遍历左子树 2. 访问根节点 3. 递归遍历右子树 """ result = [] def inorder (node ): if not node: return inorder(node.left) result.append(node.val) inorder(node.right) inorder(root) return result
迭代实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def inorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 迭代实现:使用栈模拟递归 算法步骤: 1. 从根节点开始,一直向左走到底,路径上的节点都入栈 2. 弹出栈顶节点并访问 3. 转向右子树,重复步骤 1 关键技巧: - 使用 current 指针追踪当前节点 - 当 current 为 None 且栈为空时,遍历结束 """ result = [] stack = [] current = root while current or stack: while current: stack.append(current) current = current.left current = stack.pop() result.append(current.val) current = current.right return result
执行过程示例 (树
[1, null, 2, 3]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 初始: current = 1, stack = [], result = [] 迭代 1: current = 1,向左走到底 入栈 1 -> stack = [1] current = null 弹出 1 -> result = [1] current = 2( 1 的右子节点) 迭代 2: current = 2,向左走到底 入栈 2 -> stack = [2] current = 3( 2 的左子节点) 入栈 3 -> stack = [2, 3] current = null 弹出 3 -> result = [1, 3] current = null( 3 的右子节点) 弹出 2 -> result = [1, 3, 2] current = null( 2 的右子节点) 迭代 3: current = null, stack = [],结束
常见错误
错误:迭代实现中忘记转向右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 while current or stack: while current: stack.append(current) current = current.left current = stack.pop() result.append(current.val) while current or stack: while current: stack.append(current) current = current.left current = stack.pop() result.append(current.val) current = current.right
LeetCode 145:
二叉树的后序遍历
问题描述
给定一个二叉树的根节点
root,返回它的节点值的后序遍历 。
后序遍历顺序 :左子树 → 右子树 → 根节点
示例 :
输入:root = [1,null,2,3]
输出:[3,2,1]
问题分析
后序遍历的特点是先处理左右子树,最后访问根节点 。这种顺序在删除树节点时很有用,因为需要先删除子节点才能删除父节点。
应用场景 :
删除二叉树 :先删除子树,再删除根节点
计算目录大小 :先计算子目录大小,再计算总大小
表达式树求值 :后序遍历表达式树得到后缀表达式(逆波兰式)
解题思路
递归思路 :先递归左子树,再递归右子树,最后访问根节点。
迭代思路 :后序遍历的迭代实现较复杂。方法一:使用两个栈,先按"根-右-左"顺序入栈
1,再全部弹出到栈 2,最后依次弹出栈 2
得到"左-右-根"。方法二:使用一个栈,记录每个节点的访问状态(未访问、左子树已访问、右子树已访问)。
复杂度分析
递归实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def postorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 后序遍历:左 -> 右 -> 根 算法步骤: 1. 递归遍历左子树 2. 递归遍历右子树 3. 访问根节点 """ result = [] def postorder (node ): if not node: return postorder(node.left) postorder(node.right) result.append(node.val) postorder(root) return result
迭代实现(双栈法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def postorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 迭代实现:使用两个栈 算法步骤: 1. 使用栈 1 按"根-右-左"顺序遍历 2. 将栈 1 弹出的节点压入栈 2 3. 依次弹出栈 2,得到"左-右-根"顺序 关键技巧: - 栈 1:根 -> 右 -> 左(前序遍历的镜像) - 栈 2:反转后得到 左 -> 右 -> 根(后序遍历) """ if not root: return [] stack1 = [root] stack2 = [] while stack1: node = stack1.pop() stack2.append(node) if node.left: stack1.append(node.left) if node.right: stack1.append(node.right) result = [] while stack2: result.append(stack2.pop().val) return result
为什么双栈法有效 :
栈 1 按"根-右-左"顺序遍历(前序遍历的镜像),压入栈 2 后顺序不变。栈
2 弹出时顺序反转,得到"左-右-根"(后序遍历)。
迭代实现(单栈法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def postorderTraversal (self, root: Optional [TreeNode] ) -> List [int ]: """ 迭代实现:使用一个栈,记录访问状态 算法步骤: 1. 使用栈存储(节点, 状态)元组 2. 状态: 0=未访问, 1=左子树已访问, 2=右子树已访问 3. 根据状态决定下一步操作 """ if not root: return [] result = [] stack = [(root, 0 )] while stack: node, state = stack.pop() if state == 0 : stack.append((node, 1 )) if node.left: stack.append((node.left, 0 )) elif state == 1 : stack.append((node, 2 )) if node.right: stack.append((node.right, 0 )) else : result.append(node.val) return result
LeetCode 102:
二叉树的层序遍历
问题描述
给定一个二叉树的根节点
root,返回其节点值的层序遍历 (即逐层地,从左到右访问所有节点)。
示例 :
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
问题分析
层序遍历(
BFS)按照树的层级从上到下、从左到右访问节点。需要使用队列(
FIFO)来保证访问顺序。
应用场景 :
打印二叉树 :按层级打印树结构
找最短路径 :在树中找从根到叶的最短路径
序列化二叉树 :按层级序列化树结构
解题思路
使用队列实现 BFS
。将根节点入队,然后循环:记录当前层的节点数,依次出队这些节点并访问,将它们的子节点入队。
复杂度分析
时间复杂度 : ,每个节点访问一次
空间复杂度 : ,
为树的最大宽度(队列的最大长度)
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from collections import dequedef levelOrder (self, root: Optional [TreeNode] ) -> List [List [int ]]: """ 层序遍历:使用队列实现 BFS 算法步骤: 1. 将根节点入队 2. 当队列不为空时循环: a. 记录当前层的节点数(队列长度) b. 依次出队这些节点,访问并收集到当前层结果 c. 将出队节点的子节点入队 3. 返回所有层的结果 关键技巧: - 使用 level_size 记录每层节点数,确保分层处理 - 子节点入队在下一轮循环中处理 """ if not root: return [] result = [] queue = deque([root]) while queue: level_size = len (queue) level = [] for _ in range (level_size): node = queue.popleft() level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(level) return result
执行过程示例 (树
[3,9,20,null,null,15,7]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 初始: queue = [3], result = [] 第 1 层: level_size = 1 出队 3 -> level = [3] 入队 9, 20 -> queue = [9, 20] result = [[3]] 第 2 层: level_size = 2 出队 9 -> level = [9] 出队 20 -> level = [9, 20] 入队 15, 7 -> queue = [15, 7] result = [[3], [9, 20]] 第 3 层: level_size = 2 出队 15 -> level = [15] 出队 7 -> level = [15, 7] 无子节点入队 result = [[3], [9, 20], [15, 7]] 结束: queue 为空
算法过程可视化 :下面的动画演示了层序遍历如何逐层访问节点:
常见错误
错误:忘记记录层大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 while queue: node = queue.popleft() result.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) while queue: level_size = len (queue) level = [] for _ in range (level_size): node = queue.popleft() level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(level)
LeetCode 105:
从前序与中序遍历序列构造二叉树
问题描述
给定两个整数数组 preorder 和 inorder,其中
preorder 是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 :
输入:preorder = [3,9,20,15,7],
inorder = [9,3,15,20,7]
输出:[3,9,20,null,null,15,7]
约束条件 :
preorder 和 inorder 均无重复元素
问题分析
核心洞察 :
前序遍历的第一个元素是根节点
在中序遍历中找到根节点,左边是左子树,右边是右子树
递归构造左右子树
关键步骤 :
从前序遍历中取第一个元素作为根节点
在中序遍历中找到根节点的位置
根据根节点位置,划分左右子树的范围
递归构造左子树和右子树
解题思路
使用递归 +
哈希表优化查找。哈希表存储中序遍历中每个值对应的索引,避免每次线性查找。
复杂度分析
时间复杂度 : ,每个节点访问一次,哈希表查找
空间复杂度 : ,哈希表 + 递归栈
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 def buildTree (self, preorder: List [int ], inorder: List [int ] ) -> Optional [TreeNode]: """ 从前序和中序遍历构造二叉树 算法步骤: 1. 创建哈希表,存储中序遍历中每个值的索引 2. 递归函数 build(pre_start, pre_end, in_start, in_end): a. 如果 pre_start > pre_end,返回 None b. 取 preorder[pre_start]作为根节点值 c. 在 inorder 中找到根节点的位置 root_idx d. 计算左子树大小: left_size = root_idx - in_start e. 递归构造左子树: build(pre_start+1, pre_start+left_size, in_start, root_idx-1) f. 递归构造右子树: build(pre_start+left_size+1, pre_end, root_idx+1, in_end) 边界条件: - 空数组: pre_start > pre_end - 单节点: pre_start == pre_end 优化技巧: - 使用哈希表存储 inorder 索引,避免每次线性查找 """ inorder_map = {val: idx for idx, val in enumerate (inorder)} def build (pre_start, pre_end, in_start, in_end ): if pre_start > pre_end: return None root_val = preorder[pre_start] root = TreeNode(root_val) root_idx = inorder_map[root_val] left_size = root_idx - in_start root.left = build( pre_start + 1 , pre_start + left_size, in_start, root_idx - 1 ) root.right = build( pre_start + left_size + 1 , pre_end, root_idx + 1 , in_end ) return root return build(0 , len (preorder) - 1 , 0 , len (inorder) - 1 )
索引计算详解 :
假设 preorder = [3,9,20,15,7],
inorder = [9,3,15,20,7]:
1 2 3 4 5 6 7 8 9 10 根节点: 3( preorder[0]) 在 inorder 中位置: index=1 左子树: - 前序范围:[9]( preorder[1:1+1]) - 中序范围:[9]( inorder[0:1]) 右子树: - 前序范围:[20,15,7]( preorder[2:5]) - 中序范围:[15,20,7]( inorder[2:5])
算法原理
为什么可以唯一确定二叉树 :
前序遍历告诉我们根节点的位置,中序遍历告诉我们左右子树的划分。结合这两个信息,可以唯一确定树的结构。
数学证明 :
对于 个节点的树,前序遍历有 种可能,中序遍历也有
种可能。但给定前序和中序,树的结构是唯一的(前提是节点值不重复)。
常见错误
错误一:索引计算错误
1 2 3 4 5 left_size = root_idx left_size = root_idx - in_start
错误二:忘记处理边界条件
1 2 3 4 5 6 7 8 9 def build (pre_start, pre_end, in_start, in_end ): root_val = preorder[pre_start] def build (pre_start, pre_end, in_start, in_end ): if pre_start > pre_end: return None root_val = preorder[pre_start]
LeetCode 106:
从后序与中序遍历序列构造二叉树
问题描述
给定两个整数数组 inorder 和 postorder,其中
inorder 是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗二叉树。
示例 :
输入:inorder = [9,3,15,20,7],
postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
问题分析
核心洞察 :
后序遍历的最后一个元素是根节点
在中序遍历中找到根节点,左边是左子树,右边是右子树
递归构造左右子树
与从前序构造的区别:根节点在后序遍历的末尾,而不是开头。
解题思路
类似前序+中序的构造方法,但根节点从后序遍历末尾取,右子树范围需要从后往前计算。
复杂度分析
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 def buildTree (self, inorder: List [int ], postorder: List [int ] ) -> Optional [TreeNode]: """ 从后序和中序遍历构造二叉树 算法步骤: 1. 创建哈希表存储 inorder 索引 2. 递归函数 build(in_start, in_end, post_start, post_end): a. 如果 post_start > post_end,返回 None b. 取 postorder[post_end]作为根节点值 c. 在 inorder 中找到根节点位置 root_idx d. 计算右子树大小: right_size = in_end - root_idx e. 递归构造左子树和右子树 关键区别: - 根节点在 postorder 末尾( post_end) - 右子树范围从后往前计算 """ inorder_map = {val: idx for idx, val in enumerate (inorder)} def build (in_start, in_end, post_start, post_end ): if post_start > post_end: return None root_val = postorder[post_end] root = TreeNode(root_val) root_idx = inorder_map[root_val] right_size = in_end - root_idx root.left = build( in_start, root_idx - 1 , post_start, post_end - right_size - 1 ) root.right = build( root_idx + 1 , in_end, post_end - right_size, post_end - 1 ) return root return build(0 , len (inorder) - 1 , 0 , len (postorder) - 1 )
遍历方式对比总结
遍历方式
顺序
递归实现难度
迭代实现难度
典型应用
前序遍历
根-左-右
⭐
⭐⭐
复制树、打印目录
中序遍历
左-根-右
⭐
⭐⭐
BST 有序输出、验证 BST
后序遍历
左-右-根
⭐
⭐⭐⭐
删除树、计算目录大小
层序遍历
从上到下、从左到右
⭐⭐
⭐⭐
打印树、找最短路径
记忆口诀 :
前序 :根在前(根-左-右)
中序 :根在中(左-根-右)
后序 :根在后(左-右-根)
层序 :按层来( BFS)
❓ Q&A:二叉树遍历常见疑问
Q1:为什么中序遍历 BST
得到有序序列?
A : BST 的定义是:对于每个节点,左子树所有节点值
< 节点值 < 右子树所有节点值。
中序遍历的顺序是"左-根-右",这意味着: 1.
先访问所有小于当前节点的值(左子树) 2. 再访问当前节点 3.
最后访问所有大于当前节点的值(右子树)
因此,中序遍历 BST 自然得到升序序列。
验证 :
Q2:迭代实现和递归实现哪个更好?
A :各有优劣:
维度
递归实现
迭代实现
代码简洁性
✅ 更简洁
❌ 更复杂
空间复杂度
❌ (递归栈)
✅ (显式栈)
栈溢出风险
❌ 深度大时可能溢出
✅ 可控制
理解难度
✅ 直观
❌ 需要理解栈操作
性能
相当
相当
建议 :
面试 :优先用递归(简洁),如果面试官要求迭代再改写
生产 :深度可控时用递归,深度很大时用迭代
学习 :两种都要掌握,理解本质
Q3:如何选择遍历方式?
A :根据问题需求选择:
前序遍历 适用于: - 需要先处理根节点再处理子树 -
复制树结构 - 打印目录结构
中序遍历 适用于: - BST
相关操作(查找、验证、有序输出) - 表达式树求中缀表达式
后序遍历 适用于: - 需要先处理子树再处理根节点 -
删除树节点 - 计算目录大小 - 表达式树求后缀表达式
层序遍历 适用于: - 需要按层级处理 - 找最短路径 -
序列化二叉树
Q4: Morris 遍历的优势是什么?
A : Morris 遍历的主要优势是空间复杂度 。
对比 :
方法
空间复杂度
时间复杂度
适用场景
递归
一般情况
迭代+栈
一般情况
Morris
空间受限
代价 :
代码复杂度高
会修改树结构(临时链接)
不适合多线程环境
建议 :除非空间限制严格,否则优先用递归或迭代实现。
Q5:构造二叉树时为什么需要中序遍历?
A :中序遍历提供了左右子树的划分信息 。
为什么前序+后序不能唯一确定 :
考虑两个不同的树:
前序遍历都是:[1, 2]
后序遍历都是:[2, 1]
但树结构不同!
为什么前序+中序可以唯一确定 :
前序告诉我们根节点位置
中序告诉我们左右子树划分
结合两者可以唯一确定结构
数学证明 :对于
个节点,给定前序和中序,树结构唯一(节点值不重复时)。
Q6:如何处理有重复值的二叉树?
A :有重复值时,构造可能不唯一。
问题 :如果 preorder = [1,1,1],
inorder = [1,1,1],无法唯一确定树结构。
解决方案 :
假设约束 :题目通常保证节点值唯一
如果允许重复 :需要额外信息(如节点 ID)来区分
实际应用 :通常通过节点引用而非值来区分
Q7:层序遍历的时间复杂度为什么是
?
A :每个节点恰好入队一次、出队一次。
分析 :
入队操作 : 次(每个节点一次)
出队操作 : 次(每个节点一次)
访问操作 : 次(每个节点一次)
总操作数:
空间复杂度 :队列最大长度为树的最大宽度 ,通常 $ w
n, 最 坏 情 况 ( 完 全 二 叉 树 ) w = n/2
$。
🎓 总结:二叉树遍历核心要点
记忆公式 :
前序 :根 → 左 → 右
中序 :左 → 根 → 右
后序 :左 → 右 → 根
层序 : BFS,按层访问
记忆口诀 :
前中后看根位置,层序就是 BFS
构造树需要中序,划分左右靠它来
实战 Checklist :
常见错误避免 :
✅ 递归时检查空节点
✅ 迭代时注意压栈顺序
✅ 层序遍历时记录层大小
✅ 构造树时正确计算索引范围
通过系统掌握二叉树的遍历和构造,你就能解决大部分二叉树相关的 LeetCode
问题。记住:递归是基础,迭代是进阶,理解本质是关键!