1. 二叉树基础概念与代码随想录训练营背景
作为一名参加过多次算法训练营的老学员,我清楚地记得第一次接触二叉树时那种既兴奋又困惑的感觉。代码随想录算法训练营第71期的Day13课程,正是许多算法初学者突破二叉树难关的关键节点。让我们先明确几个基本概念:
二叉树(Binary Tree)是每个节点最多有两个子节点的树结构,这两个子节点分别称为左孩子和左子树、右孩子和右子树。在算法面试中,二叉树相关题目出现的频率高得惊人——根据我的统计,大厂技术面试中约35%的算法题都与二叉树相关。
代码随想录训练营的特色在于其"五步刷题法":
- 理解题目本质
- 确定解题方法
- 手写代码实现
- 调试与优化
- 总结与举一反三
这种训练方式特别适合二叉树的掌握,因为二叉树问题往往有多种解法,需要系统化的思维方式。在Day13的课程中,通常会从最基础的二叉树遍历开始,这是后续所有复杂操作的基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二叉树的三种基本遍历方式
2.1 前序遍历(Preorder Traversal)
前序遍历的顺序是:根节点 → 左子树 → 右子树。在实际编码面试中,前序遍历常用于复制二叉树的结构。
python复制def preorderTraversal(root):
result = []
def traversal(node):
if not node:
return
result.append(node.val) # 访问根节点
traversal(node.left) # 遍历左子树
traversal(node.right) # 遍历右子树
traversal(root)
return result
注意:递归实现虽然简洁,但在面试中通常会被要求写出非递归版本。这是因为递归可能引发栈溢出,且非递归实现能更好地展示你对调用栈的理解。
2.2 中序遍历(Inorder Traversal)
中序遍历的顺序是:左子树 → 根节点 → 右子树。对于二叉搜索树(BST),中序遍历会得到一个有序序列。
python复制def inorderTraversal(root):
result = []
stack = []
curr = root
while curr or stack:
while curr: # 深入左子树
stack.append(curr)
curr = curr.left
curr = stack.pop()
result.append(curr.val) # 访问节点
curr = curr.right # 转向右子树
return result
这个非递归实现使用了显式的栈来模拟递归过程。在训练营中,老师通常会强调这种实现方式的几个关键点:
- 外层循环条件
curr or stack确保处理完所有节点 - 内层循环将左节点全部压栈
- 弹出栈顶节点后立即转向其右子树
2.3 后序遍历(Postorder Traversal)
后序遍历的顺序是:左子树 → 右子树 → 根节点。这种遍历常用于释放二叉树内存或计算表达式树。
python复制def postorderTraversal(root):
result = []
stack = []
prev = None
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack[-1]
if not root.right or root.right == prev:
result.append(root.val)
stack.pop()
prev = root
root = None
else:
root = root.right
return result
后序遍历的非递归实现最为复杂,需要记录前一个访问的节点(prev)来判断右子树是否已处理。在训练营实践中,很多学员会在这里卡壳,关键是要理解:
- 当节点的右子树为空或已访问时,才能访问该节点
- 否则应该先处理右子树
3. 二叉树遍历的实战应用
3.1 根据遍历序列重建二叉树
训练营Day13的一个重要练习是根据前序和中序遍历序列重建二叉树。这是考察对遍历顺序理解的经典题目。
python复制def buildTree(preorder, inorder):
if not preorder or not inorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
idx = inorder.index(root_val)
root.left = buildTree(preorder[1:idx+1], inorder[:idx])
root.right = buildTree(preorder[idx+1:], inorder[idx+1:])
return root
这个递归解法看似简单,但有几个易错点:
- 前序遍历的第一个元素总是当前子树的根节点
- 在中序遍历中找到该根节点,左侧即为左子树,右侧为右子树
- 前序遍历中左子树的长度与中序遍历中左子树的长度相同
3.2 二叉树的层序遍历
层序遍历(Level Order Traversal)使用队列实现,是广度优先搜索(BFS)在二叉树中的应用。
python复制from collections import deque
def levelOrder(root):
if not root:
return []
queue = deque([root])
result = []
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
在训练营中,老师会强调这种实现的两个关键优化:
- 使用
level_size记录当前层的节点数,确保正确分层 - 使用双端队列
deque提高popleft()的效率
4. 二叉树常见问题与解题技巧
4.1 二叉树的最大深度
计算二叉树的最大深度是训练营Day13的基础练习之一,但其中蕴含着重要的递归思维。
python复制def maxDepth(root):
if not root:
return 0
left_depth = maxDepth(root.left)
right_depth = maxDepth(root.right)
return max(left_depth, right_depth) + 1
这个简洁的递归实现展示了分治思想:
- 空树的深度为0
- 非空树的深度是其左右子树深度的最大值加1
4.2 对称二叉树的判断
判断二叉树是否对称是训练营中常见的面试题,考察对二叉树结构的理解。
python复制def isSymmetric(root):
if not root:
return True
def compare(left, right):
if not left and not right:
return True
if not left or not right:
return False
if left.val != right.val:
return False
return compare(left.left, right.right) and compare(left.right, right.left)
return compare(root.left, root.right)
这个解法采用递归比较左右子树:
- 两个空节点是对称的
- 只有一个空节点不对称
- 节点值不同不对称
- 递归比较外侧和内侧子树
4.3 平衡二叉树的判断
平衡二叉树是指左右子树高度差不超过1的二叉树,判断方法如下:
python复制def isBalanced(root):
def check(node):
if not node:
return 0
left = check(node.left)
right = check(node.right)
if left == -1 or right == -1 or abs(left - right) > 1:
return -1
return max(left, right) + 1
return check(root) != -1
这个解法在计算高度的同时进行平衡性检查,避免了重复计算:
- 如果子树不平衡,直接返回-1
- 否则返回正常高度
- 最终检查整棵树的高度是否为-1
5. 二叉树算法实战中的常见陷阱
在训练营的实践环节,我发现有几个常见错误反复出现:
5.1 空指针异常
处理二叉树时最容易忽略空节点检查。例如在遍历时忘记判断if not node,导致访问node.val时抛出异常。
5.2 递归终止条件错误
递归解法必须明确终止条件。常见错误包括:
- 忘记处理空节点情况
- 终止条件设置不当导致无限递归
5.3 遍历顺序混淆
特别是在处理前序、中序、后序遍历时,容易混淆访问节点的时机。一个记忆技巧:
- 前序:访问在递归左子树之前
- 中序:访问在递归左右子树之间
- 后序:访问在递归右子树之后
5.4 修改树结构时的指针丢失
在反转二叉树等需要修改指针的操作中,容易丢失原始指针。正确做法是先保存子节点引用:
python复制def invertTree(root):
if root:
left = root.left # 先保存引用
right = root.right
root.left = invertTree(right)
root.right = invertTree(left)
return root
6. 二叉树算法的进阶思考
6.1 莫里斯遍历(Morris Traversal)
这是一种空间复杂度为O(1)的遍历方法,通过修改树结构(临时链接)实现:
python复制def inorderMorrisTraversal(root):
curr = root
result = []
while curr:
if not curr.left:
result.append(curr.val)
curr = curr.right
else:
pre = curr.left
while pre.right and pre.right != curr:
pre = pre.right
if not pre.right:
pre.right = curr
curr = curr.left
else:
pre.right = None
result.append(curr.val)
curr = curr.right
return result
莫里斯遍历的核心思想是利用空闲的右指针指向后继节点,遍历完成后恢复树结构。虽然复杂,但在内存受限的环境中很有价值。
6.2 线索二叉树
线索二叉树通过在空指针位置存储前驱或后继节点信息,可以加速遍历操作。虽然现代计算机内存充足,这种优化意义不大,但理解其思想有助于深入掌握二叉树结构。
6.3 二叉树的序列化与反序列化
在实际工程中,经常需要将二叉树结构转换为字符串存储或传输:
python复制def serialize(root):
if not root:
return "None"
left = serialize(root.left)
right = serialize(root.right)
return f"{root.val},{left},{right}"
def deserialize(data):
def helper(queue):
val = queue.popleft()
if val == "None":
return None
node = TreeNode(int(val))
node.left = helper(queue)
node.right = helper(queue)
return node
queue = deque(data.split(","))
return helper(queue)
这种先序遍历的序列化方式虽然简单,但能完整保存树结构信息。在分布式系统中,这种技术常用于传输复杂数据结构。
