一、题目分析
题目要求:
给定一棵二叉树,定义一个“好节点”为:从根节点到该节点路径上,没有任何节点的值比该节点的值大。要求我们返回二叉树中好节点的数量。
示例:
-
示例 1:

输入: [3,1,4,3,null,1,5] 输出: 4解释: 根节点
3是好节点。节点4是路径3->4中的最大值,节点5是路径3->4->5中的最大值,节点3是路径3->1->3中的最大值,所以总共有 4 个好节点。 -
示例 2:

输入: [3,3,null,4,2] 输出: 3解释: 根节点
3是好节点。节点4是路径3->4中的最大值。节点3是路径3->3中的最大值。节点2不是好节点,因为路径上有个节点的值比2大。 -
示例 3:
输入: [1] 输出: 1解释: 根节点是好节点。
1448. 统计二叉树中好节点的数目 - 力扣(LeetCode)
二、解题思路
我们需要遍历整棵二叉树,并在遍历的过程中记录当前路径上遇到的最大值。对于每个节点,如果当前节点的值大于等于当前路径的最大值,那么这个节点就是“好节点”。
三、代码实现
我们可以通过 深度优先搜索(DFS) 来实现这个遍历,并且在递归的过程中传递当前路径上的最大值。
1. 递归 DFS 实现
递归的 DFS 方法非常适合这种遍历二叉树的问题。
代码如下:
class Solution {
public:
int goodNodes(TreeNode* root, int maxVal = INT_MIN) {
if (!root) return 0;
int cnt = 0;
if (root->val >= maxVal) {
cnt++;
maxVal = root->val;
}
cnt += goodNodes(root->left, maxVal);
cnt += goodNodes(root->right, maxVal);
return cnt;
}
};
代码讲解:
- 递归边界: 如果当前节点为空(
nullptr),返回0,表示没有“好节点”。 - 判断当前节点是否为“好节点”: 如果当前节点的值大于等于
maxVal(当前路径的最大值),那么这个节点就是好节点,cnt加1,同时更新maxVal为当前节点的值。 - 递归遍历左、右子树: 继续遍历当前节点的左、右子树,传递更新后的
maxVal。 - 累加“好节点”数量: 返回左右子树的“好节点”数量之和。
示例分析
假设有如下二叉树:
3
/ \
1 4
/ / \
3 1 5
- 根节点 3: 从根到自己是好节点,
maxVal = 3,计数1。 - 左子节点 1: 路径
[3, 1],1 <maxVal,不是好节点。 - 左子节点的左子节点 3: 路径
[3, 1, 3],3 >=maxVal,是好节点,计数2。 - 右子节点 4: 路径
[3, 4],4 >maxVal,是好节点,更新maxVal为4,计数3。 - 右子节点的左子节点 1: 路径
[3, 4, 1],1 <maxVal,不是好节点。 - 右子节点的右子节点 5: 路径
[3, 4, 5],5 >maxVal,是好节点,计数4。
最终,输出 4。
2. 迭代 DFS 实现
如果使用栈来实现迭代的 DFS,需要额外注意在回溯到父节点时如何正确恢复 maxVal。
迭代代码:
class Solution {
public:
int goodNodes(TreeNode* root) {
if (!root) return 0;
stack<pair<TreeNode*, int>> s; // 栈中存放节点和当前路径的最大值
s.push({root, root->val});
int cnt = 0;
while (!s.empty()) {
auto [node, maxVal] = s.top();
s.pop();
if (node->val >= maxVal) {
cnt++;
maxVal = node->val; // 更新路径最大值
}
if (node->right) s.push({node->right, maxVal});
if (node->left) s.push({node->left, maxVal});
}
return cnt;
}
};
代码讲解:
-
栈的定义:使用一个栈
s,栈中的每个元素是一个pair,其中first是当前遍历的节点,second是到达该节点时路径中的最大值。stack<pair<TreeNode*, int>> s; -
初始值:将根节点和它的值作为初始最大值一起入栈。
s.push({root, root->val}); -
遍历过程:每次从栈顶取出一个元素,比较当前节点的值和当前路径的最大值。
while (!s.empty()) { auto [node, maxx] = s.top(); s.pop(); // 判断当前节点是否为好节点 if (node->val >= maxx) { cnt++; } // 更新路径最大值 maxx = max(maxx, node->val); // 将左右子节点入栈 if (node->right) s.push({node->right, maxx}); if (node->left) s.push({node->left, maxx}); }
注意细节:
- 路径中的最大值传递:在遍历到每个节点时,必须确保路径中的最大值被正确传递到子节点。如果不正确传递,可能会导致判断错误。
- 遍历顺序:由于栈的特性(后进先出),在处理当前节点时,需要先将右子节点入栈,然后再将左子节点入栈。这保证了在模拟递归时,左子树会先于右子树遍历。
- 初始值的选择:栈的初始状态需要包含根节点和根节点的值,因为根节点的值在它自己的路径中始终是最大的。
假设输入的二叉树是[3, 1, 4, 3, null, 1, 5],即树的结构如下:
结合示例解释
3
/ \
1 4
/ / \
3 1 5
- 初始化:
s.push({root, 3}),栈初始为[(3, 3)]。 - 第一步:弹出
(3, 3),3 >= 3,是好节点,cnt++,maxx保持为3,将右子节点(4, 3)和左子节点(1, 3)入栈。 - 第二步:弹出
(1, 3),1 < 3,不是好节点,maxx保持为3,将子节点(3, 3)入栈。 - 第三步:弹出
(3, 3),3 >= 3,是好节点,cnt++,maxx保持为3,无子节点。 - 第四步:弹出
(4, 3),4 > 3,是好节点,cnt++,更新maxx = 4,将右子节点(5, 4)和左子节点(1, 4)入栈。 - 第五步:弹出
(1, 4),1 < 4,不是好节点,maxx保持为4,无子节点。 - 第六步:弹出
(5, 4),5 > 4,是好节点,cnt++,maxx更新为5,无子节点。
最终结果:cnt = 4。
四、总结
通过 DFS 遍历二叉树,实时跟踪当前路径的最大值来判断节点是否为“好节点”。递归与迭代两种方法各有优势,递归写法简洁直观,迭代写法则适用于避免递归栈溢出。理解如何在 DFS 中维护状态是解决二叉树类问题的关键。

















![P2858 [USACO06FEB] Treats for the Cows](https://img-blog.csdnimg.cn/img_convert/4a635da433117d939892d99909bd311d.png)

