1. 问题背景与理解
最近在刷二叉树相关的题目时,遇到了一个挺有意思的问题——"1302 层数最深叶子节点的和"。这个题目看似简单,但想要写出高效且优雅的解法,还是需要动一番脑筋的。今天我就来分享一下我的解题思路和实现过程。
首先,我们需要明确题目的具体要求:给定一个二叉树的根节点 root,返回其最深层叶子节点的和。也就是说,我们需要找到二叉树中深度最大的那些叶子节点(可能有多个),然后把它们的值相加。
举个例子:
code复制 1
/ \
2 3
/ \ \
4 5 6
/ \
7 8
在这个二叉树中,最深层是第4层,叶子节点是7和8,所以它们的和是15。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析
2.1 暴力解法:两次遍历
最直观的想法是分两步走:
- 第一次遍历找出树的最大深度
- 第二次遍历收集所有等于最大深度的叶子节点并求和
这种方法虽然直观,但需要遍历树两次,时间复杂度是O(2n)=O(n),空间复杂度取决于树的形状,最坏情况下是O(n)。
python复制def deepestLeavesSum(root):
max_depth = findMaxDepth(root)
return sumNodesAtDepth(root, max_depth)
def findMaxDepth(node):
if not node:
return 0
return 1 + max(findMaxDepth(node.left), findMaxDepth(node.right))
def sumNodesAtDepth(node, depth):
if not node:
return 0
if depth == 1:
return node.val
return sumNodesAtDepth(node.left, depth-1) + sumNodesAtDepth(node.right, depth-1)
2.2 优化思路:一次遍历
能不能只遍历一次树就得到结果呢?当然可以!我们可以采用深度优先搜索(DFS)的方式,在遍历时记录当前深度和最大深度:
- 如果当前节点是叶子节点:
- 如果当前深度 > 最大深度:更新最大深度,重置和为当前节点值
- 如果当前深度 == 最大深度:将当前节点值加到和中
- 如果当前深度 < 最大深度:不做任何操作
- 递归处理左右子树
这种方法只需要遍历树一次,时间复杂度是O(n),空间复杂度仍然是O(n)。
python复制def deepestLeavesSum(root):
total = 0
max_depth = 0
def dfs(node, depth):
nonlocal total, max_depth
if not node:
return
if not node.left and not node.right:
if depth > max_depth:
max_depth = depth
total = node.val
elif depth == max_depth:
total += node.val
return
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return total
2.3 广度优先搜索(BFS)解法
除了DFS,我们还可以使用BFS来解这个问题。思路是:
- 使用队列进行层次遍历
- 每次处理一层时,先记录当前层的和
- 最后一层的和就是我们要的结果
这种方法同样只需要遍历一次树,时间复杂度O(n),空间复杂度在最坏情况下是O(n)(当树是完全二叉树时)。
python复制from collections import deque
def deepestLeavesSum(root):
if not root:
return 0
queue = deque([root])
level_sum = 0
while queue:
level_sum = 0
level_size = len(queue)
for _ in range(level_size):
node = queue.popleft()
level_sum += node.val
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return level_sum
3. 代码实现与优化
3.1 DFS实现细节
让我们更详细地看看DFS的实现。关键点在于:
- 使用nonlocal变量来在递归函数中修改外部变量
- 只在叶子节点处进行判断和累加
- 递归时深度+1
这种实现方式简洁明了,但需要注意Python中nonlocal的使用限制。在类的方法中,我们可以使用实例变量来替代nonlocal。
python复制class Solution:
def deepestLeavesSum(self, root):
self.total = 0
self.max_depth = 0
def dfs(node, depth):
if not node:
return
if not node.left and not node.right:
if depth > self.max_depth:
self.max_depth = depth
self.total = node.val
elif depth == self.max_depth:
self.total += node.val
return
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return self.total
3.2 BFS实现优化
BFS的实现可以稍作优化,避免在最后一层时还进行不必要的操作:
python复制def deepestLeavesSum(root):
if not root:
return 0
queue = [root]
while queue:
next_level = []
current_sum = 0
for node in queue:
current_sum += node.val
if node.left:
next_level.append(node.left)
if node.right:
next_level.append(node.right)
if not next_level: # 如果没有下一层了,当前层就是最深层
return current_sum
queue = next_level
return 0
这种实现方式更加直观,当发现没有下一层时立即返回当前层的和,避免了额外的判断。
4. 复杂度分析与比较
让我们比较一下几种方法的性能:
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 两次DFS | O(n) | O(h) | 思路简单直接 | 需要遍历树两次 |
| 一次DFS | O(n) | O(h) | 只需一次遍历 | 需要维护额外状态 |
| BFS | O(n) | O(w) | 层次遍历直观 | 最坏情况下空间较大 |
其中:
- n是树中节点总数
- h是树的高度
- w是树的最大宽度(最宽一层的节点数)
对于大多数情况,三种方法的时间复杂度都是O(n),差别不大。空间复杂度上:
- DFS的空间复杂度取决于树的高度,适合高瘦的树
- BFS的空间复杂度取决于树的宽度,适合矮胖的树
在实际应用中,可以根据树的形状特点选择更适合的算法。
5. 边界条件与测试用例
为了确保我们的解法正确,需要考虑各种边界情况:
- 空树:应该返回0
- 只有根节点:返回根节点的值
- 所有叶子节点在同一层:返回所有叶子节点的和
- 最深层有多个叶子节点:返回它们的和
- 最深层只有一个叶子节点:返回它的值
测试用例示例:
python复制# 测试用例1:空树
assert deepestLeavesSum(None) == 0
# 测试用例2:只有根节点
root = TreeNode(1)
assert deepestLeavesSum(root) == 1
# 测试用例3:示例中的树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.right = TreeNode(6)
root.left.left.left = TreeNode(7)
root.right.right.right = TreeNode(8)
assert deepestLeavesSum(root) == 15
# 测试用例4:所有叶子在同一层
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
assert deepestLeavesSum(root) == 5
6. 实际应用与扩展
这个问题虽然看起来简单,但它涉及了二叉树遍历的基本操作,在实际开发中有很多应用场景:
- 文件系统操作:计算最深目录中的文件大小总和
- 组织结构分析:找出公司最底层员工的总薪资
- 游戏AI:评估决策树中最深层可能的结果
我们可以对这个题目进行一些扩展:
- 找出最深层所有叶子节点而不仅仅是求和
- 找出每一层叶子节点的和
- 找出从根到最深叶子节点的路径
例如,要找出最深层所有叶子节点,我们可以修改DFS解法:
python复制def deepestLeaves(root):
result = []
max_depth = 0
def dfs(node, depth):
nonlocal max_depth, result
if not node:
return
if not node.left and not node.right:
if depth > max_depth:
max_depth = depth
result = [node.val]
elif depth == max_depth:
result.append(node.val)
return
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return result
7. 性能优化技巧
在实际应用中,我们可以考虑以下优化技巧:
- 对于特别大的树,可以考虑迭代式的DFS实现,避免递归深度过大导致的栈溢出
- 如果树的结构经常变化但需要频繁查询最深叶子节点和,可以考虑缓存结果
- 并行处理:对于非常大的树,可以将子树分配给不同线程处理
迭代式DFS实现示例:
python复制def deepestLeavesSum(root):
if not root:
return 0
max_depth = 0
total = 0
stack = [(root, 0)]
while stack:
node, depth = stack.pop()
if not node.left and not node.right:
if depth > max_depth:
max_depth = depth
total = node.val
elif depth == max_depth:
total += node.val
else:
if node.right:
stack.append((node.right, depth + 1))
if node.left:
stack.append((node.left, depth + 1))
return total
8. 不同语言的实现
虽然我们主要用Python实现,但这个问题在其他语言中的实现也值得了解:
8.1 Java实现
java复制class Solution {
int maxDepth = 0;
int sum = 0;
public int deepestLeavesSum(TreeNode root) {
dfs(root, 0);
return sum;
}
private void dfs(TreeNode node, int depth) {
if (node == null) return;
if (node.left == null && node.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
sum = node.val;
} else if (depth == maxDepth) {
sum += node.val;
}
return;
}
dfs(node.left, depth + 1);
dfs(node.right, depth + 1);
}
}
8.2 JavaScript实现
javascript复制function deepestLeavesSum(root) {
let maxDepth = 0;
let sum = 0;
function dfs(node, depth) {
if (!node) return;
if (!node.left && !node.right) {
if (depth > maxDepth) {
maxDepth = depth;
sum = node.val;
} else if (depth === maxDepth) {
sum += node.val;
}
return;
}
dfs(node.left, depth + 1);
dfs(node.right, depth + 1);
}
dfs(root, 0);
return sum;
}
8.3 C++实现
cpp复制class Solution {
public:
int deepestLeavesSum(TreeNode* root) {
int max_depth = 0;
int sum = 0;
dfs(root, 0, max_depth, sum);
return sum;
}
void dfs(TreeNode* node, int depth, int& max_depth, int& sum) {
if (!node) return;
if (!node->left && !node->right) {
if (depth > max_depth) {
max_depth = depth;
sum = node->val;
} else if (depth == max_depth) {
sum += node->val;
}
return;
}
dfs(node->left, depth + 1, max_depth, sum);
dfs(node->right, depth + 1, max_depth, sum);
}
};
9. 常见错误与调试技巧
在实现这个算法时,容易犯的一些错误:
- 忘记处理空树的情况
- 深度计算错误(从0开始还是从1开始)
- 在BFS实现中,错误地重置每层的和
- 在DFS实现中,错误地使用局部变量而不是nonlocal/实例变量
调试技巧:
- 打印树的遍历过程,确保访问节点的顺序正确
- 在递归函数中加入深度打印,确认深度计算正确
- 对于BFS,可以打印每层的节点值和当前和
例如,可以在DFS实现中加入调试打印:
python复制def deepestLeavesSum(root):
total = 0
max_depth = 0
def dfs(node, depth):
nonlocal total, max_depth
if not node:
return
print(f"Visiting {node.val} at depth {depth}")
if not node.left and not node.right:
print(f"Leaf node {node.val} at depth {depth}")
if depth > max_depth:
print(f"New max depth: {depth}, reset sum to {node.val}")
max_depth = depth
total = node.val
elif depth == max_depth:
print(f"Adding {node.val} to sum, new sum: {total + node.val}")
total += node.val
return
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return total
10. 总结与个人心得
通过这个题目,我们深入探讨了二叉树遍历的几种方式及其应用。在实际编程中,我有以下几点体会:
- 对于树的问题,首先要明确遍历方式(DFS/BFS),根据问题特点选择最合适的方法
- 递归解法通常更简洁,但需要注意递归深度和状态管理
- 迭代解法(使用栈或队列)可以避免递归带来的潜在问题,但代码可能稍复杂
- 边界条件的处理非常重要,特别是对于空输入和极端情况的考虑
- 调试树相关问题时,可视化或打印遍历过程非常有帮助
这个题目虽然标为中等难度,但它很好地考察了对二叉树遍历的理解和应用能力。建议在掌握基本解法后,尝试自己进行一些扩展和变种练习,比如找出最深节点的路径而不仅仅是求和,这样可以更全面地提升解决树相关问题的能力。
