1. 题目背景与需求分析
今天要拆解的是力扣第515题"在每个树行中找最大值"。这是一道典型的二叉树层序遍历应用题,题目要求我们找出二叉树每一层中的最大值,并以列表形式返回。看似简单的需求背后,其实考察了我们对二叉树遍历和层级处理的理解深度。
这道题在力扣题库中被归类为中等难度,实际提交通过率约为67.3%。从我的刷题经验来看,它非常适合用来巩固层序遍历的各种变种应用。很多同学在掌握了基础的层序遍历后,遇到这种需要记录层级信息的变种题时,往往会突然卡壳。这正是我们需要重点突破的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二叉树层序遍历的核心思路
2.1 标准层序遍历模板
在解决这个问题前,我们先回顾下标准的层序遍历实现。层序遍历(BFS)通常使用队列来实现,其基本模板如下:
python复制def levelOrder(root):
if not root:
return []
queue = collections.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
这个模板有几个关键点需要注意:
- 使用双端队列(deque)来优化popleft()操作的时间复杂度
- 在每层开始前记录当前队列长度,这就是该层的节点数
- 处理完一个节点后立即将其子节点加入队列
2.2 本题的变通思路
对于本题,我们只需要在标准模板的基础上做一个小改动:不再记录整层的节点值,而是维护一个变量来追踪当前层的最大值。具体来说:
- 在进入每层循环时初始化一个max_val变量
- 在处理该层每个节点时更新max_val
- 在该层处理完成后将max_val加入结果列表
这种"模板+微调"的思路在刷题中非常实用。很多题目都是在经典算法的基础上进行适当变形,掌握这种思维方式能大大提高解题效率。
3. 完整代码实现与逐行解析
3.1 Python实现版本
python复制import collections
def largestValues(root):
if not root:
return []
queue = collections.deque([root])
result = []
while queue:
level_size = len(queue)
level_max = float('-inf')
for _ in range(level_size):
node = queue.popleft()
level_max = max(level_max, node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level_max)
return result
3.2 关键代码解析
让我们重点分析几个关键部分:
-
初始化处理:首先检查根节点是否为空,这是二叉树题目的常规操作。空树直接返回空列表。
-
队列初始化:使用collections.deque而非常规列表,因为它的popleft()操作是O(1)时间复杂度,而列表的pop(0)是O(n)。
-
层级最大值追踪:
level_max = float('-inf'):初始化为负无穷,确保任何节点值都能覆盖它- 在遍历该层每个节点时,通过
max(level_max, node.val)动态更新最大值
-
子节点处理:与标准层序遍历一致,先左后右地将非空子节点加入队列
-
结果收集:每层处理完成后,将该层的最大值加入结果列表
3.3 复杂度分析
- 时间复杂度:O(n),每个节点恰好被访问一次
- 空间复杂度:O(n),最坏情况下队列需要存储最后一层的所有节点(约n/2)
4. 常见错误与调试技巧
4.1 新手常犯的错误
根据我的刷题和教学经验,初学者在这道题上容易犯以下几个错误:
- 忘记处理空树情况:直接开始遍历导致空指针异常
- 层级划分不清晰:没有使用level_size记录当前层节点数,导致层级混淆
- 最大值初始化不当:使用0而不是负无穷初始化,遇到负值节点时出错
- 结果收集时机错误:在每层循环外部而非内部收集结果
4.2 调试技巧分享
当你的代码出现问题时,可以尝试以下调试方法:
- 打印层级信息:在处理每个节点时打印其值和当前层级
python复制print(f"Processing node value: {node.val} at level {len(result)}")
- 可视化小树结构:手动构建一个3层的小二叉树,在纸上模拟算法流程
code复制 1
/ \
3 2
/ \ \
5 3 9
- 边界测试用例:
- 空树输入
- 只有根节点的树
- 所有节点值相同的树
- 包含负值的树
5. 算法优化与变种思考
5.1 DFS替代方案
虽然BFS是这类问题的自然解法,但使用DFS也可以实现,只是需要额外记录节点的深度:
python复制def largestValues(root):
result = []
def dfs(node, depth):
if not node:
return
if depth == len(result):
result.append(node.val)
else:
result[depth] = max(result[depth], node.val)
dfs(node.left, depth+1)
dfs(node.right, depth+1)
dfs(root, 0)
return result
这种方法的优势是空间复杂度在最坏情况下为O(h),h是树高,对于平衡树更优。但实际运行时间通常比BFS稍慢。
5.2 相关题目拓展
掌握这道题后,可以尝试解决以下类似题目来巩固层序遍历的应用:
- 二叉树的右视图(199题)
- 二叉树的层平均值(637题)
- 二叉树的锯齿形层序遍历(103题)
- 在每个树行中找最小值(变种)
- 在每个树行中找平均值(变种)
这些题目都可以使用相似的模板稍作修改来解决,建议按这个顺序逐一攻克。
6. 工程实践中的注意事项
在实际工程项目中处理类似需求时,还需要考虑以下方面:
- 大数据量处理:如果树非常大,需要考虑内存限制,可能需要分批处理
- 并行化可能:层序遍历理论上可以并行处理不同子树
- 树结构的序列化:如何将数据库或网络中的树结构转换为内存中的二叉树对象
- 自定义比较逻辑:最大值比较可能需要支持自定义比较函数
例如,处理商品分类树时找出每层价格最高的商品,就需要灵活调整比较逻辑。
7. 刷题策略建议
根据我多年的刷题和面试经验,针对这类二叉树题目,建议:
- 先理解再记忆:真正理解层序遍历的工作原理比死记硬背代码更重要
- 模板化学习:建立自己的算法模板库,遇到变种题时快速调整
- 举一反三:每做完一道题,思考它可以如何变形,对应力扣哪些其他题目
- 可视化工具辅助:使用二叉树可视化工具(如LeetCode Playground)观察算法执行过程
记住,刷题的目的不是单纯追求数量,而是培养解决问题的通用能力。这道515题就是一个很好的培养这种能力的媒介。
