递归遍历是最直接的解法,核心是判断节点是否为叶子(左右子节点均为nullptr),空节点返回0,叶子节点返回1,否则递归累加左右子树结果;迭代法用栈模拟,需避免访问空指针。

递归遍历是最直接的解法
叶子节点指左右子节点都为 nullptr 的节点,所以核心判断逻辑就是:node->left == nullptr && node->right == nullptr。递归天然适配树的结构,每层只关心当前节点是否为叶子,再把子树结果累加即可。
注意不要在空节点(nullptr)上做成员访问,否则会崩溃。必须先判空再访问子节点。
- 遇到
nullptr,返回 0 - 遇到叶子节点(左右均为
nullptr),返回 1 - 否则返回
countLeaves(node->left) + countLeaves(node->right)
int countLeaves(TreeNode* node) {
if (!node) return 0;
if (!node->left && !node->right) return 1;
return countLeaves(node->left) + countLeaves(node->right);
}
迭代写法要用栈模拟递归过程
用栈保存待处理节点,每次弹出一个节点,检查是否为叶子;不是叶子就将非空子节点压栈。和递归逻辑一致,只是手动管理调用栈。
容易漏掉的是:只压入非空子节点,否则会在后续访问 nullptr->left 导致段错误。
- 初始化栈时只压入根节点(若非空)
- 循环中对每个
node,先判断!node->left && !node->right - 仅当
node->left非空才压入,右子节点同理
int countLeaves(TreeNode* root) {
if (!root) return 0;
stack<treenode> s;
s.push(root);
int count = 0;
while (!s.empty()) {
TreeNode* node = s.top(); s.pop();
if (!node->left && !node->right) count++;
if (node->left) s.push(node->left);
if (node->right) s.push(node->right);
}
return count;
}</treenode>
避免常见误判:空树、单节点、只有左/右子树
这几种边界情况最容易暴露逻辑漏洞。比如把“有左子树”当成“不是叶子”,就会错判单右子树节点;或把空树返回值设为 1。
- 空树(
root == nullptr)→ 返回 0 - 仅根节点(
root存在,但left和right均为nullptr)→ 返回 1 - 根有左子树但无右子树 → 左子树内部继续判断,根不计入叶子
建议用这几个小样例手跑一遍:空指针、new TreeNode(1)、new TreeNode(1, new TreeNode(2), nullptr)。
要不要用成员变量或引用传参来计数?
可以,但没必要。递归本身已天然支持“分治+聚合”,用返回值累加更清晰、线程安全、不易出错。如果改用全局变量或引用参数,反而要额外初始化、重置,且在多线程或多次调用场景下容易埋坑。
唯一适合引用传参的场景是:你同时还要统计深度、宽度等其他信息,想一次遍历全拿到——但那就不是单纯算叶子数了。
真正容易被忽略的是:所有路径最终都要走到叶子或空节点,递归终止条件必须覆盖这两种情况,少一个都会导致未定义行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











