必须用一次后序遍历同时获取叶子数、所有根到叶路径和树高:叶子数用整型计数,路径用vector回溯累积(进入push、离开pop),高度通过返回值自底向上计算(空树高-1);递归函数需返回int以保证高度正确合并,避免引用传参破坏纯度。

怎么一次性遍历就拿到叶子数、所有路径和树高
不能靠三次独立遍历——那样时间复杂度是 O(3n),而且重复递归开销大。必须用一次后序遍历同时收集三类信息:叶子数量用整型计数,路径用 vector<vector>></vector> 累积,高度用返回值层层上报。
关键点在于:路径需要回溯(进入节点 push,离开时 pop),而高度和叶子计数只依赖子树返回值,不需回溯。
-
TreeNode结构里必须有left和right成员,否则编译不过 - 空树(
root == nullptr)时,叶子数为 0、路径为空、高度为 -1(按惯例:空树高度 -1,单节点树高度 0) - 路径保存的是“从根到叶”的完整序列,不是节点值集合,顺序错就全错
为什么递归函数要返回 int 而不是 void
因为树高必须自底向上计算:height = max(left_height, right_height) + 1。如果函数返回 void,就只能靠引用参数传高度,但这样会破坏函数纯度,且容易在多线程或嵌套调用中出错。
实际写法是让递归函数返回当前子树高度,同时通过引用参数带出叶子数和路径:
立即学习“C++免费学习笔记(深入)”;
int dfs(TreeNode* root, int& leaf_count, vector<vector<int>>& paths, vector<int>& path) {
if (!root) return -1;
path.push_back(root->val);
int left_h = dfs(root->left, leaf_count, paths, path);
int right_h = dfs(root->right, leaf_count, paths, path);
// 判断是否为叶子
if (!root->left && !root->right) {
leaf_count++;
paths.push_back(path);
}
path.pop_back(); // 回溯
return max(left_h, right_h) + 1;
}注意:path.pop_back() 必须放在判断叶子之后,否则叶子节点的值不会被存入 paths。
迭代写法能避免栈溢出,但路径和高度怎么同步维护
用栈模拟递归时,不能只存 TreeNode*,必须打包状态:当前节点、当前路径副本、当前已知左右子树高度(或标记是否已处理过子树)。
推荐用结构体封装:
struct StackNode {
TreeNode* node;
vector<int> path;
int left_height;
int right_height;
bool visited; // 是否已处理完左右子树
};难点在于:高度必须等左右子树都算完才能合并;路径只在叶子处保存;叶子计数可直接累加。迭代逻辑比递归多 3 倍代码量,且易错点集中在 visited 标志位更新时机和高度赋值顺序。
- 非叶子节点出栈两次:第一次压入左右子,第二次才计算高度并返回
- 每次压栈都要拷贝
path,空间开销明显大于递归(递归复用同一path容器) - 若树深度超 10⁵,递归确实可能爆栈,但此时迭代也要小心内存爆炸
测试时最容易漏掉的边界情况
不是空树、不是完美二叉树、甚至不是二叉搜索树——这些都不影响统计逻辑,但容易让人误判结果。真正要盯紧的是这三种:
- 单节点树:
leaf_count == 1,paths.size() == 1,height == 0 - 只有左子树(或只有右子树)的链状树:叶子数仍为 1,但路径长度等于节点数,高度也等于节点数减 1
- 含重复值的树:路径内容看结构,不看值,所以
[1,2,2]和[1,2,2]是两条不同路径(只要分支不同)
调试时建议先打印 paths 内容,再核对 leaf_count 是否等于 paths.size(),最后用 height 反推最大路径长度是否匹配(最大路径长度 = height + 1)。


















