1. 二叉树基础与LeetCode刷题指南
作为一名刷过300+道LeetCode题的老手,我深刻理解二叉树在面试中的重要性。根据统计,国内一线互联网公司的技术面试中,约65%的算法题都与树结构相关,其中二叉树占比最高。对于刚接触算法的新手来说,从二叉树简单题入手是最佳选择。
LeetCode题库中标记为"Easy"的二叉树题目共有47道(截至2023年8月),这些题目看似简单,实则暗藏玄机。它们不仅是检验递归思维的试金石,更是帮助我们掌握Python/Java等语言特性的绝佳素材。本文将带你系统梳理这些题目,并穿插讲解刷题过程中必备的语法技巧。
提示:建议配合LeetCode的"Explore"卡片中的"Binary Tree 101"专题一起学习,效果更佳。
2. 二叉树核心概念与遍历框架
2.1 二叉树数据结构解析
二叉树(Binary Tree)是每个节点最多有两个子节点的树结构,通常称为左子节点和右子节点。在LeetCode题目中,二叉树节点的定义通常如下:
python复制class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
这个简单的数据结构却蕴含着强大的表达能力。理解以下几点至关重要:
- 节点间的父子关系构成层次结构
- 空节点(None/null)表示树的边界
- 节点值可以是任意可比较的数据类型
2.2 三种基本遍历方式
二叉树遍历是解决几乎所有相关问题的基础,必须熟练掌握以下三种递归实现:
前序遍历(Pre-order)
python复制def preorder(root):
if not root: return
print(root.val) # 先访问根节点
preorder(root.left) # 再递归左子树
preorder(root.right) # 最后递归右子树
中序遍历(In-order)
python复制def inorder(root):
if not root: return
inorder(root.left) # 先递归左子树
print(root.val) # 再访问根节点
inorder(root.right) # 最后递归右子树
后序遍历(Post-order)
python复制def postorder(root):
if not root: return
postorder(root.left) # 先递归左子树
postorder(root.right) # 再递归右子树
print(root.val) # 最后访问根节点
记忆技巧:前/中/后指的是根节点在遍历中的位置顺序
2.3 层序遍历(BFS)实现
广度优先搜索(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
这个模板可以解决诸如"二叉树的层序遍历"(LeetCode 102)、"二叉树的右视图"(LeetCode 199)等问题。
3. LeetCode简单题分类解析
3.1 基础属性计算类题目
104. 二叉树的最大深度
python复制def maxDepth(root):
if not root: return 0
return 1 + max(maxDepth(root.left), maxDepth(root.right))
111. 二叉树的最小深度
python复制def minDepth(root):
if not root: return 0
if not root.left: return 1 + minDepth(root.right)
if not root.right: return 1 + minDepth(root.left)
return 1 + min(minDepth(root.left), minDepth(root.right))
222. 完全二叉树的节点个数
python复制def countNodes(root):
if not root: return 0
return 1 + countNodes(root.left) + countNodes(root.right)
注意事项:最小深度的计算需要特别注意单边子树为空的情况,这是常见的错误点
3.2 对称性与镜像问题
101. 对称二叉树
python复制def isSymmetric(root):
def check(p, q):
if not p and not q: return True
if not p or not q: return False
return p.val == q.val and check(p.left, q.right) and check(p.right, q.left)
return check(root.left, root.right)
226. 翻转二叉树
python复制def invertTree(root):
if root:
root.left, root.right = invertTree(root.right), invertTree(root.left)
return root
这类题目考察对二叉树结构的理解,关键在于设计正确的递归终止条件和递归逻辑。
3.3 路径与求和问题
112. 路径总和
python复制def hasPathSum(root, targetSum):
if not root: return False
if not root.left and not root.right: return root.val == targetSum
return hasPathSum(root.left, targetSum - root.val) or hasPathSum(root.right, targetSum - root.val)
257. 二叉树的所有路径
python复制def binaryTreePaths(root):
def dfs(node, path):
if not node: return
path += str(node.val)
if not node.left and not node.right:
paths.append(path)
return
path += "->"
dfs(node.left, path)
dfs(node.right, path)
paths = []
dfs(root, "")
return paths
路径类问题通常需要维护当前路径状态,注意回溯时的状态恢复。
4. 必备语法技巧详解
4.1 Python中的可变对象陷阱
在解决"113. 路径总和 II"这类需要记录路径的题目时,新手常犯的错误:
python复制# 错误示范
def pathSum(root, targetSum):
def dfs(node, path):
if not node: return
path.append(node.val) # 直接修改了原始列表
if not node.left and not node.right and sum(path) == targetSum:
res.append(path.copy())
dfs(node.left, path)
dfs(node.right, path)
path.pop()
res = []
dfs(root, [])
return res
正确做法是传递path的拷贝:
python复制def dfs(node, path):
if not node: return
new_path = path + [node.val] # 创建新列表
if not node.left and not node.right and sum(new_path) == targetSum:
res.append(new_path)
dfs(node.left, new_path)
dfs(node.right, new_path)
4.2 Java中的null处理
在Java中处理二叉树时,null检查是必须的:
java复制// 计算二叉树最大深度
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
4.3 递归与迭代转换技巧
很多递归解法可以转换为迭代实现,例如前序遍历:
python复制# 递归版
def preorderTraversal(root):
res = []
def helper(node):
if not node: return
res.append(node.val)
helper(node.left)
helper(node.right)
helper(root)
return res
# 迭代版(使用栈)
def preorderTraversal(root):
if not root: return []
stack, res = [root], []
while stack:
node = stack.pop()
res.append(node.val)
if node.right: stack.append(node.right) # 右子节点先入栈
if node.left: stack.append(node.left) # 左子节点后入栈
return res
5. 常见错误与调试技巧
5.1 递归终止条件错误
典型错误示例(计算最大深度):
python复制def maxDepth(root):
if not root: return 0
return maxDepth(root.left) + maxDepth(root.right) # 错误:应该取max而不是sum
5.2 变量作用域混淆
在Python中处理类变量时容易犯的错误:
python复制class Solution:
def sumOfLeftLeaves(self, root):
total = 0 # 错误:递归调用会重置这个值
def helper(node):
if not node: return
if node.left and not node.left.left and not node.left.right:
total += node.left.val # UnboundLocalError
helper(node.left)
helper(node.right)
helper(root)
return total
正确做法是使用nonlocal或类变量:
python复制class Solution:
def sumOfLeftLeaves(self, root):
self.total = 0
def helper(node):
if not node: return
if node.left and not node.left.left and not node.left.right:
self.total += node.left.val
helper(node.left)
helper(node.right)
helper(root)
return self.total
5.3 测试用例设计建议
针对二叉树题目,建议准备以下测试用例:
- 空树(None/null)
- 单节点树
- 完全倾斜的树(只有左子树或只有右子树)
- 普通平衡树
- 带负值的树
- 值全部相同的树
例如测试最大深度:
python复制def test_maxDepth():
# 测试用例1:空树
assert maxDepth(None) == 0
# 测试用例2:单节点树
root = TreeNode(1)
assert maxDepth(root) == 1
# 测试用例3:完全左倾斜树
root = TreeNode(1, TreeNode(2, TreeNode(3)))
assert maxDepth(root) == 3
# 测试用例4:普通平衡树
root = TreeNode(1, TreeNode(2), TreeNode(3))
assert maxDepth(root) == 2
6. 刷题策略与进阶路线
6.1 高效刷题四步法
- 理解题目:画图分析示例,确保完全理解题意
- 设计算法:在白板上写出伪代码,分析时间/空间复杂度
- 实现代码:转换为实际代码,注意边界条件
- 测试验证:用多种测试用例验证代码正确性
6.2 二叉树题目进阶路线
- 基础遍历(前/中/后序,层序)
- 属性计算(深度、节点数、平衡性)
- 路径与求和问题
- 构造与序列化问题
- 二叉搜索树特性应用
6.3 推荐题目练习顺序
-
- 二叉树的最大深度
-
- 对称二叉树
-
- 路径总和
-
- 翻转二叉树
-
- 二叉树的所有路径
-
- 相同的树
-
- 将有序数组转换为二叉搜索树
-
- 平衡二叉树
-
- 二叉树的最小深度
-
- 完全二叉树的节点个数
在实际面试中,二叉树题目往往作为热身题出现。我建议每天保持2-3道二叉树的练习量,持续2周后,你会发现自己对递归的理解和代码实现能力都有显著提升。