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

news2024/9/21 8:04:21

一、题目分析

题目要求:

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

示例:

  • 示例 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;
    }
};

代码讲解:

  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

迭代代码:

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
  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 中维护状态是解决二叉树类问题的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Open3D 遍历八叉树

目录 一、概述 1.1原理 1.2实现步骤 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2数据显示 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概…

Git安装包及怎么再windows上运行

第一步&#xff1a;下载git。 国内 Git for Windows. 国内镜像 感谢GitHub - waylau/git-for-win: Git for Windows. 国内直接从官网下载比较困难&#xff0c;需要翻墙。这里提供一个国内的下载站&#xff0c;方便网友下载 安装步骤&#xff1a; Git for Windows安装和基本…

VTK—vtkCutter截取平面数据

&#xff0c; 本例 vtkCutter可以配合隐式函数截取数据使用vtkPlane隐式函数配合vtkWidget截取任意平面。 1.读入数据 Create(vtkMultiBlockPLOT3DReader, reader);reader->SetXYZFileName("G:/Temp/vtkTest/combxyz.bin");reader->SetQFileName("G:/Tem…

Linux系统之部署轻量级Markdown文本编辑器

Linux系统之部署轻量级Markdown文本编辑器 一、项目介绍1.1 项目简介1.2 使用方法 二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源 四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看ap…

【题目/训练】:双指针

引言 我们已经在这篇博客【算法/学习】双指针-CSDN博客里面讲了双指针、二分等的相关知识。 现在我们来做一些训练吧 经典例题 1. 移动零 思路&#xff1a; 使用 0 当做这个中间点&#xff0c;把不等于 0(注意题目没说不能有负数)的放到中间点的左边&#xff0c;等于 0 的…

微服务设计原则——高性能:存储设计

文章目录 1.读写分离2.分库分表3.动静分离4.冷热分离5.重写轻读6.数据异构参考文献 任何一个系统&#xff0c;从单机到分布式&#xff0c;从前端到后台&#xff0c;功能和逻辑各不相同&#xff0c;但干的只有两件事&#xff1a;读和写。而每个系统的业务特性可能都不一样&#…

STM32CubeMX生成stm32MP135中断优先级配置错误修正方法

0 修改方法 使用STM32CubeMX生成stm32MP135代码的中断优先级配置错误&#xff0c;将导致所有中断优先级设置不对。 如果设置EXTI0中断优先级为10&#xff0c;在STM32CubeMX中配置如下&#xff1a; 生成的中断优先级配置代码为&#xff1a; 正确写法应该将中断优先级左移3位&…

python人工智能001:NumPy科学计算库说明与安装

1. NumPy说明 NumPy&#xff08;Numerical Python&#xff09;是Python的一个开源数值计算扩展库。它提供了一个强大的N维数组对象ndarray&#xff0c;以及用于对这些数组进行操作的函数。NumPy的数组和数组操作是Python数据分析、机器学习、科学计算等领域的基础。 NumPy的主…

web开发环境搭配与创建javaee项目

一.web开发 (1)web开发指的是前端,后端,以及数据库进行交互&#xff0c;前端发送请求到后端&#xff0c;后端经过程序处理后到达数据库&#xff0c;最后在进行后端处理响应回前端。 (2)一次三端交互的doget或者dopost简单请求流程 (3)web开发除了需要前端,后端,数据库开发工具…

Java之线程篇一

目录 如何理解进程&#xff1f; 进程和线程的区别 线程的优点 线程的缺点 线程异常 线程用途 创建线程 方法一&#xff1a;继承Thread类&#xff0c;重写run() 观察线程 小结 方法二&#xff1a; 实现Runnable接口&#xff0c;重写run() 方法三&#xff1a;继承Threa…

超越AnimateAnyone!Meta提出全身 3D虚拟人技术ExAvatar,可通过简短视频克隆人像并转化为3D数字形象

ExAvatar是由DGIST和Meta公司的Codec Avatars Lab联合研发的一项技术,能够通过捕捉视频中的动作和表情,转化为栩栩如生的3D数字形象。这项技术解决了以往技术中的难题,提高了动画的自然度和渲染效果。 什么是 ExAvatar? ExAvatar 是全新富有表现力的全身 3D 高斯化身。 结…

2.1 文件内容差异对比方法

2.1 文件内容差异对比方法 文件内容差异对比方法2.1.1 两个字符串的差异对比2.1.2 生成美观的HTML格式文档2.1.3 对比nginx 配置文件差异代码封装 文件内容差异对比方法 介绍如何通过difflib模块实现文件内容差异对比。difflib作为Python的标准库模块无需安装&#xff0c;作用…

算法学习——树形DP——多叉树的最长路径

文章目录 引言正文0、题目的具体内容1、树的直径定理推导3、使用数组链表表示树使用数组表示链表数组表示单链表头插法演示数组表示单链表在索引k出插入一个数字数组表示链表实现代码链表表示树 4、树形DP的具体分析 总结 引言 这个问题&#xff0c;每一次都会痛击我&#xff…

养猫劝退?猫咪掉毛严重怎么办?用宠物空气净化器高效清理浮毛

不瞒大家说&#xff0c;养猫以来&#xff0c;我中途有无数次想要把它送人的想法&#xff0c;最终还是敌不过它的可爱留下来了。为什么会产生这样的念头呢&#xff1f;罪魁祸首就是猫毛问题。夏天是猫咪的换毛季&#xff0c;它们为了散热会脱去厚重的毛发&#xff0c;进入疯狂掉…

06结构型设计模式——代理模式

一、代理模式简介 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff08;GoF书中解释结构型设计模式&#xff1a;一种用来处理类或对象、模块的组合关系的模式&#xff09;&#xff0c;代理模式是其中的一种&#xff0c;它可以为其他对象提供一种代…

依赖倒置原则详解

依赖倒置原则详解 一、引言 在大型系统架构设计中&#xff0c;依赖倒置原则&#xff08;Dependency Inversion Principle&#xff0c;DIP&#xff09;被广泛视为增强系统灵活性和可维护性的核心原则之一。最近在架构设计审查中&#xff0c;我们经常遇到由于依赖关系设计不当导…

叠Buff!经典麻雀优化算法+多重双向深度学习!SSA-BiTCN-BiGRU-Attention多输入单输出回归预测

叠Buff!经典麻雀优化算法多重双向深度学习&#xff01;SSA-BiTCN-BiGRU-Attention多输入单输出回归预测 目录 叠Buff!经典麻雀优化算法多重双向深度学习&#xff01;SSA-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matl…

P2858 [USACO06FEB] Treats for the Cows

题目描述 约翰经常给产奶量高的奶牛发特殊津贴&#xff0c;于是很快奶牛们拥有了大笔不知该怎么花的钱。为此&#xff0c;约翰购置了 N 份美味的零食来卖给奶牛们。每天约翰售出一份零食。当然约翰希望这些零食全部售出后能得到最大的收益。这些零食有以下这些有趣的特性&…

(python)动态类型语言的灵活性和动态性

前言 一种具有动态类型,动态绑定&#xff0c;动态执行和灵活的对象模型的编程语言。它能够适应不断变化的编程需求的&#xff0c;是否会受欢迎? 动态语言的优点 灵活性高&#xff1a;开发过程更加灵活和快捷&#xff0c;不需要在编写代码前严格定义变量和对象的类型&#xff0…

linux之prometheus+grafana

Prometheus介绍 Prometheus(普罗米修斯)是一套开源的监控&报警&时间序列数据库的组合, 由go语言开发。 适合监控容器平台, 因为kubernetes(俗称k8s)的流行带动了prometheus的发展。 PS&#xff1a;由于目前还未学习容器&#xff0c;所以在今天的课程里使用prometheus监…