1. 项目背景与问题定义
"1004 Counting Leaves"这个题目乍看简单,实则蕴含了树形结构遍历的经典算法问题。作为一名经常刷题的开发者,我第一次看到这个标题时,脑海中立即浮现出家族族谱、公司组织架构这类层级数据的统计场景。
这个问题的核心是:给定一棵树结构,要求统计每一层叶子节点的数量。就像数一棵真实树木的落叶,我们需要从树根开始,逐层记录没有子节点的末端节点数。在实际编程中,这类问题常见于层级数据处理、目录结构分析等场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构选择与算法设计
2.1 树的表示方法
对于这个问题,最常见的树表示方法有两种:
- 邻接表:使用字典或数组存储每个节点的子节点
- 节点对象:构建包含children指针的节点类
考虑到输入格式通常是节点间的父子关系,我推荐使用邻接表:
python复制tree = {
1: [2, 3],
2: [4, 5],
3: [6],
4: [],
5: [7, 8],
6: [],
7: [],
8: []
}
2.2 遍历算法选择
广度优先搜索(BFS)天然适合按层统计的需求。以下是BFS的标准实现框架:
python复制from collections import deque
def count_leaves(tree, root):
if not tree:
return []
result = []
queue = deque([(root, 0)]) # (node, level)
while queue:
node, level = queue.popleft()
# 处理当前层级的统计逻辑
# ...
for child in tree[node]:
queue.append((child, level + 1))
return result
3. 完整解决方案实现
3.1 核心算法实现
基于上述框架,完整的叶子节点统计实现如下:
python复制from collections import deque
def count_leaves(tree, root):
if not tree:
return []
result = []
current_level = 0
count = 0
queue = deque([(root, 0)])
while queue:
node, level = queue.popleft()
if level > current_level:
result.append(count)
current_level = level
count = 0
if not tree[node]: # 叶子节点
count += 1
for child in tree[node]:
queue.append((child, level + 1))
# 添加最后一层的统计
if count > 0 or not result:
result.append(count)
return result
3.2 输入输出处理
实际编程题通常需要处理特定输入格式。假设输入格式为:
- 第一行:节点总数N(非叶子节点数 < N < 100)
- 随后N行:每个节点的子节点列表
处理代码示例:
python复制def main():
N = int(input())
tree = {}
for i in range(N):
parts = input().split()
node_id = i + 1
children = list(map(int, parts[1:])) if len(parts) > 1 else []
tree[node_id] = children
root = 1 # 通常根节点是1
counts = count_leaves(tree, root)
print(' '.join(map(str, counts)))
if __name__ == '__main__':
main()
4. 算法优化与边界处理
4.1 时间复杂度分析
标准的BFS时间复杂度为O(N),其中N是节点总数。每个节点恰好被访问一次,空间复杂度主要取决于队列的大小,最坏情况下也是O(N)。
4.2 特殊边界情况
需要特别注意以下几种边界情况:
- 空树:直接返回空列表
- 只有根节点:返回[1]
- 单边树(所有节点都只有一个子节点):最后一层才有一个叶子节点
4.3 测试用例设计
完善的测试应该包含:
python复制test_cases = [
# (tree, expected)
({1: []}, [1]), # 只有根节点
({1: [2], 2: []}, [1]), # 两层单节点
({1: [2,3], 2: [], 3: []}, [2]), # 标准二叉树
({1: [2,3], 2: [4], 3: [], 4: []}, [1, 1]), # 不平衡树
({}, []), # 空树
]
5. 实际应用场景扩展
虽然题目看似简单,但这类层级统计在实际开发中应用广泛:
- 组织架构分析:统计每个职级的员工数量
- 商品分类:统计末级分类的商品数
- 文件系统:统计每层目录下的文件数
- 社交网络:统计各度好友的数量
我在实际项目中就曾用类似算法分析过电商平台的商品分类体系,帮助运营发现分类过深的品类,优化用户体验。
6. 常见错误与调试技巧
6.1 典型错误模式
- 层数统计错误:容易在层级切换时漏计或重复计数
- 最后一层遗漏:循环结束后忘记处理最后一层的计数
- 根节点为叶子:当树只有根节点时,需要特殊处理
6.2 调试建议
- 打印遍历路径:在BFS循环中加入print语句,输出当前节点和层级
- 可视化小树:手工绘制3-4个节点的小树,逐步验证算法
- 边界测试:务必测试单节点、空树等特殊情况
提示:在IDE中设置断点调试时,可以watch这两个变量:
- current_level:跟踪当前处理层级
- count:观察当前层叶子数的累计过程
7. 算法变种与进阶思考
掌握了基础版本后,可以尝试以下变种问题:
- 统计每层所有节点数(不仅是叶子)
- 找出树的最大宽度(节点最多的层)
- 同时统计叶子和非叶子节点
- 使用DFS实现相同功能(需要额外记录层级信息)
对于DFS实现,核心思路是:
python复制def dfs(node, level, result):
if level >= len(result):
result.append(0)
if not tree[node]:
result[level] += 1
for child in tree[node]:
dfs(child, level + 1, result)
8. 性能优化实践
对于超大规模树结构(节点数>1M),可以考虑以下优化:
- 迭代代替递归:避免栈溢出
- 并行处理:对子树进行并行BFS
- 内存优化:使用位图标记已访问节点
我在处理百万级商品分类数据时,就采用了分块+并行的优化方案,使处理时间从分钟级降到秒级。
9. 不同语言的实现差异
虽然算法思想相同,但不同语言的实现各有特点:
9.1 Java实现
java复制// 使用Queue接口和LinkedList实现
Queue<Map.Entry<Integer, Integer>> queue = new LinkedList<>();
9.2 C++实现
cpp复制// 使用pair和queue
queue<pair<int, int>> q;
9.3 JavaScript实现
javascript复制// 使用数组模拟队列
const queue = [[root, 0]];
10. 学习资源推荐
想深入掌握树形结构算法,我推荐:
- 《算法导论》树形结构章节
- LeetCode树形结构专题(100+题目)
- Visualgo.net的可视化演示
- 北京大学《数据结构与算法》公开课
这个看似简单的"数叶子"问题,其实包含了算法设计的多个重要方面:数据结构选择、遍历算法应用、边界条件处理以及实际工程优化。掌握它,你就掌握了解决更复杂树形问题的基础钥匙。
