一次递归返回叶子数和高度可避免重复遍历,需正确处理空节点(高度为-1)、结构化绑定简化接收,迭代需双栈维护状态,模板实现要前置声明节点类型,边界测试须覆盖空树、单节点、链表等场景。

递归遍历中同时统计叶子数和树高
不能分开两次遍历——那样时间复杂度翻倍,而且容易漏掉空树或单节点的边界处理。核心思路是让一次递归返回两个值:leaf_count 和 height,用 std::pair<int int></int> 或自定义结构体封装。
常见错误是把叶子判断写成 !node->left && !node->right 却忘了先判 node == nullptr,导致段错误;或者在空节点时返回 {0, 0},结果树高被算成 0(实际应为 -1 或 0,取决于定义)。
- 推荐定义:空树高度为
-1,单节点树高度为0,这样height = 1 + max(left_height, right_height)逻辑统一 - 叶子节点只在左右子树都为空时计数,且该节点非空
- C++17 可用结构化绑定简化接收:
auto [leaves, h] = dfs(node);
避免重复计算的迭代写法(DFS栈模拟)
递归虽简洁,但深树可能栈溢出;迭代需手动维护状态,难点在于:每个节点要记住自己是否已访问过子树、当前累计的叶子数、子树高度。直接用 std::stack<treenode></treenode> 不够,得存三元组。
典型错误是只压入节点指针,却在弹出时无法区分“刚进栈”还是“左右子树已处理完”,导致重复计数或高度错乱。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 推荐栈元素类型:
std::stack<:tuple int>></:tuple>,分别存节点、左子树叶子数、左子树高度(右子树同理,需二次入栈或用标记) - 更稳妥做法:用两个栈,
nodes存节点,states存对应状态(0=未处理,1=左已处理,2=全处理) - 迭代版本叶子统计逻辑不变,但高度必须从底向上合成,不能像递归那样自然回溯
模板类实现时如何支持任意数据类型
写成 template<typename t></typename> 没问题,但要注意:叶子统计与高度计算不依赖 T 的具体值,所以无需重载比较或拷贝操作;真正影响的是节点结构定义方式。
很多人卡在模板里怎么定义节点,结果硬套 struct Node { T val; Node* left; Node* right; };,却忘了 Node* 在模板实例化前无法确定大小,编译报错 invalid use of incomplete type。
- 必须前置声明
template<typename t> struct TreeNode;</typename>,再定义模板类内部的TreeNode - 或者干脆不嵌套,让用户传入节点类型,用
using Node = TreeNode<int>;</int>解耦 - 高度和叶子数函数应设计为静态成员或独立函数,避免依赖
this指针引发生命周期误判
测试时最容易忽略的边界组合
写完代码跑 root = new TreeNode(1) 通过了,不代表正确——真正难测的是空树、只有左/右子树、退化为链表、以及含空子节点的混合结构。
例如:输入 root = [1,null,2,3,null,null,4](LeetCode 风格数组),若解析逻辑没处理好 null 占位,会构造出错误树形,导致高度多算一层。
- 必测用例:
nullptr→ 叶子数 0,高度 -1(或 0,看约定) -
[1]→ 叶子数 1,高度 0 -
[1,2,null,3]→ 叶子数 1(只有节点 3),高度 2 - 高度计算若按“边数”定义,则空树为 -1;若按“节点层数”,则空树为 0 —— 必须文档化约定,否则和别人对接时出错
实际写的时候,优先用递归+pair 返回,90% 场景够用;真遇到栈深度限制再切迭代;模板部分别急着泛化,先用 int 跑通逻辑,再抽离类型参数。高度和叶子数看似简单,但空指针、定义歧义、状态同步这三点,一个没对齐就全崩。

















