一、题目分析
题目要求:
给定一棵二叉树,定义一个"好节点"为:从根节点到该节点路径上,没有任何节点的值比该节点的值大。要求我们返回二叉树中好节点的数量。
示例:
-
示例 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
解释: 根节点是好节点。
二、解题思路
我们需要遍历整棵二叉树,并在遍历的过程中记录当前路径上遇到的最大值。对于每个节点,如果当前节点的值大于等于当前路径的最大值,那么这个节点就是"好节点"。
三、代码实现
我们可以通过 深度优先搜索(DFS) 来实现这个遍历,并且在递归的过程中传递当前路径上的最大值。
1. 递归 DFS 实现
递归的 DFS 方法非常适合这种遍历二叉树的问题。
代码如下:
cpp
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
。
迭代代码:
cpp
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
是到达该节点时路径中的最大值。cppstack<pair<TreeNode*, int>> s;
-
初始值:将根节点和它的值作为初始最大值一起入栈。
cpps.push({root, root->val});
-
遍历过程:每次从栈顶取出一个元素,比较当前节点的值和当前路径的最大值。
cppwhile (!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 中维护状态是解决二叉树类问题的关键。