深度优先搜索:如何在二叉树中找出“好节点”【迭代法、状态管理技巧、DFS】

一、题目分析

题目要求:

给定一棵二叉树,定义一个"好节点"为:从根节点到该节点路径上,没有任何节点的值比该节点的值大。要求我们返回二叉树中好节点的数量。

示例:

  • 示例 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 方法非常适合这种遍历二叉树的问题。

代码如下:

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;
    }
};

代码讲解:

  1. 递归边界: 如果当前节点为空(nullptr),返回 0,表示没有"好节点"。
  2. 判断当前节点是否为"好节点": 如果当前节点的值大于等于 maxVal(当前路径的最大值),那么这个节点就是好节点,cnt1,同时更新 maxVal 为当前节点的值。
  3. 递归遍历左、右子树: 继续遍历当前节点的左、右子树,传递更新后的 maxVal
  4. 累加"好节点"数量: 返回左右子树的"好节点"数量之和。

示例分析

假设有如下二叉树:

       3
      / \
     1   4
    /   / \
   3   1   5
  1. 根节点 3: 从根到自己是好节点,maxVal = 3,计数 1
  2. 左子节点 1: 路径 [3, 1],1 < maxVal,不是好节点。
  3. 左子节点的左子节点 3: 路径 [3, 1, 3],3 >= maxVal,是好节点,计数 2
  4. 右子节点 4: 路径 [3, 4],4 > maxVal,是好节点,更新 maxVal4,计数 3
  5. 右子节点的左子节点 1: 路径 [3, 4, 1],1 < maxVal,不是好节点。
  6. 右子节点的右子节点 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 是到达该节点时路径中的最大值。

    cpp 复制代码
    stack<pair<TreeNode*, int>> s;
  • 初始值:将根节点和它的值作为初始最大值一起入栈。

    cpp 复制代码
    s.push({root, root->val});
  • 遍历过程:每次从栈顶取出一个元素,比较当前节点的值和当前路径的最大值。

    cpp 复制代码
    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
  1. 初始化s.push({root, 3}),栈初始为 [(3, 3)]
  2. 第一步 :弹出 (3, 3)3 >= 3,是好节点,cnt++maxx 保持为 3,将右子节点 (4, 3) 和左子节点 (1, 3) 入栈。
  3. 第二步 :弹出 (1, 3)1 < 3,不是好节点,maxx 保持为 3,将子节点 (3, 3) 入栈。
  4. 第三步 :弹出 (3, 3)3 >= 3,是好节点,cnt++maxx 保持为 3,无子节点。
  5. 第四步 :弹出 (4, 3)4 > 3,是好节点,cnt++,更新 maxx = 4,将右子节点 (5, 4) 和左子节点 (1, 4) 入栈。
  6. 第五步 :弹出 (1, 4)1 < 4,不是好节点,maxx 保持为 4,无子节点。
  7. 第六步 :弹出 (5, 4)5 > 4,是好节点,cnt++maxx 更新为 5,无子节点。

最终结果:cnt = 4

四、总结

通过 DFS 遍历二叉树,实时跟踪当前路径的最大值来判断节点是否为"好节点"。递归与迭代两种方法各有优势,递归写法简洁直观,迭代写法则适用于避免递归栈溢出。理解如何在 DFS 中维护状态是解决二叉树类问题的关键。

相关推荐
დ旧言~25 分钟前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF29 分钟前
投资策略规划最优决策分析
分布式·算法·金融
The_Ticker1 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan1 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi2 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
用户37791362947553 小时前
【循环神经网络】只会Python,也能让AI写出周杰伦风格的歌词
人工智能·算法
福大大架构师每日一题3 小时前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言