算法学习(十四)—— 二叉树的深度搜索(DFS)
目录
关于dfs
部分OJ题详解
2331. 计算布尔二叉树的值
129. 求根节点到叶节点数字之和
814. 二叉树剪枝
98. 验证二叉搜索树
230. 二叉搜索树中第K小的元素
257. 二叉树的所有路径
关于dfs
算法学习(十二)—— 递归,搜索,回溯前置_数组递归查找-CSDN博客
部分OJ题详解
2331. 计算布尔二叉树的值
2331. 计算布尔二叉树的值 - 力扣(LeetCode)


题目有点长,得结合示例来理解。下面来分析下这道题:
- 看到二叉树遍历,最先想到的就是递归,这里我们用DFS来尝试解决
- 在这道题的树中,只要节点有孩子,那必定有两个子节点,没有只有一个子节点的情况;非叶结点里面存的是“逻辑与”或“逻辑或”,叶子节点存的就是1或0,也就是“true”或“false”
- 然后就是发现子问题的过程了,以根节点为例,假如我要知道根节点最后的值是什么,那我就得直到左子树和右子树的值是多少,然后以左子树或右子树的根节点为例,再往下进行同样的分析,就发现了我们的相同子问题,所以我们的函数头就是:bool dfs(root); 也就是你给我一个根节点,我告诉你最后的结果是true还是false
- 然后就是函数体的设计,bool left = dfs(root->left); bool right = dfs(root->right); 也就是要告诉我们左右子树的值,然后就是根据目前的根节点是“逻辑与”还是“逻辑或”,算出结果
- 最后就是递归出口,就是当我遇到叶子节点的时候,直接返回true或false即可
class Solution {
public:bool evaluateTree(TreeNode* root) {return dfs(root); }bool dfs(TreeNode* root){if(root->left == nullptr) return root->val == 0 ? false : true;bool left = dfs(root->left);bool right = dfs(root->right);if(root->val == 2) return left | right;else return left & right;}
};
129. 求根节点到叶节点数字之和
129. 求根节点到叶节点数字之和 - 力扣(LeetCode)


下面来分析下这道题:
- 我们仍然还是尝试用递归的dfs来尝试解决下,还是那三个步骤:找相同子问题,搞清楚每个子问题要做什么,递归出口
- 首先是相同子问题,上面示例的树太小了,我们换个大的树,如下图:

- 所以函数头设计为:int dfs(root, presum); 也就是传一个根节点,以及这个根节点前面路径的值presum传给后面,然后给我返回这个根节点最后计算的值
- 函数体的步骤就是:①先算出该根节点加上前面路径的计算结果值 ②把这个值传给左子树 ③把这个值传给右子树 ④左右子树返回计算结果
- 递归出口就是当遇到叶子节点时,把上面的第一步计算完之后,再正式返回,不再将这个值传给左右子树
class Solution {
public:int sumNumbers(TreeNode* root) {return dfs(root, 0);}int dfs(TreeNode* root, int prevsum){prevsum = prevsum * 10 + root->val; //①if(root->left == nullptr && root->right == nullptr) return prevsum; //递归出口int ret = 0;if(root->left != nullptr) ret += dfs(root->left, prevsum); //②if(root->right != nullptr) ret += dfs(root->right, prevsum); //③return ret; //④}
};
814. 二叉树剪枝
814. 二叉树剪枝 - 力扣(LeetCode)

题目的意思很明确,就是如果一个节点的子树中没有值为1的节点的话,就把这个子树干掉,下面我们来分析下这道题:
- 这个题目和我们之前的题目有点不一样,前面的题目就是让我们根据信息求值,没有改变二叉树,而这个题目是要求我们实实在在的对二叉树做了修改了。还是用递归的dfs来尝试解决这道题还是三个问题
- 先来看下如何找到相同的子问题:当我遍历到一个节点时,我们要做的就是“这个节点是否应该被剪掉”,依据就是“这个节点的子树是否含有值为1的节点”,所以这道题一定是后序遍历,因为只有后序遍历才有机会回到根节点的时候带着左右子树的信息
- 当遍历到叶节点时,已经没有左右子树了,并且如果我自己也是0,就把我自己干掉,回到父节点后,还要修改下父节点的指针,这一步可以通过返回值解决,就是把父节点的指针修改成这个返回值即可,所以函数头需要有一个返回值Node*
- 如果已经是叶节点,但我自己是1,那么就不需要把我干掉,就把我自己的指针向上返回,就一直通过上面的步骤,就可以干掉所有符合要求的子树,所以函数头的设计就是:Node* dfs(root);
- 然后就是函数体的设置,基本步骤和上面的一样:①处理左子树 ②处理右子树 ③判断是否为叶结点并且判断是否为1,进行剪枝操作并返回
- 最后是递归出口,就是当root == null 的时候,直接返回
class Solution {
public:TreeNode* pruneTree(TreeNode* root) {return dfs(root);}TreeNode* dfs(TreeNode* root){if(root == nullptr) return root;root->left = dfs(root->left);root->right = dfs(root->right);if(root->left == nullptr && root->right == nullptr && root->val == 0)root = nullptr;return root;}
};
98. 验证二叉搜索树
98. 验证二叉搜索树 - 力扣(LeetCode)


这道题我们主要是理解三个点:①理解全局变量的优势 ②回溯 ③剪枝;后面会给出两种思路和代码,一个是有剪枝的,一种没有剪枝,下面来分析下这道题:
- 二叉搜索树的中序遍历结果,是一个有序的序列,所以,我们可以验证这棵树的中序遍历结果是否有序即可,这个可以用一个全局变量来搞
- 先搞一个全局变量 “prev = 负无穷大”,然后每次中序遍历时,都更新一下 prev的值,以上面的示例2为例,根据中序遍历,假设遍历到4的位置,prev此时的值为3,然后将此时节点的值和prev作比较验证有序即可,最后就是遍历到5,prev再次更新为4,再次比较即可,所以只要和prev作比较然后返回true或false即可
策略一,也就是上面的方法:
- 验证左子树是二叉搜索树
- 当前遍历的节点符合二叉搜索树的定义
- 验证右子树是二叉搜索树
策略二,剪枝:
- 还是以示例二为例,假设递归已经完成了验证左子树是二叉搜索树,此时prev是5
- 当中序遍历到3时,3小于5,所以已经不符合二叉搜索树的定义了,此时我们直接一路返回false到根节点,不再去比较节点6了,这就是剪枝,意义就是加快搜索过程
无剪枝代码:
class Solution
{
public:long prev = LONG_MIN;bool isValidBST(TreeNode* root) {return dfs(root); }bool dfs(TreeNode* root){if(root == nullptr) return true;bool left = dfs(root->left);bool cur = false; //cur表示当前节点是否满足二叉搜索树条件if(root->val > prev) cur = true;prev = root->val;bool right = dfs(root->right);return left && right && cur;}
};
剪枝:
class Solution
{
public:long prev = LONG_MIN;bool isValidBST(TreeNode* root) {return dfs(root); }bool dfs(TreeNode* root){if(root == nullptr) return true;bool left = dfs(root->left);if(left == false) return false; //剪枝:就是当左子树已经不符合搜索二叉树了,直接返回bool cur = false; //cur代表当前节点是否满足二叉搜索树条件if(root->val > prev) cur = true;if(cur == false) return false; //如果我当前位置节点已经不符合二叉搜索树要求了,直接返回,不再去遍历右子树了,这就是剪枝prev = root->val;bool right = dfs(root->right);return left && right && cur;}
};
230. 二叉搜索树中第K小的元素
230. 二叉搜索树中第 K 小的元素 - 力扣(LeetCode)

这道题题意还是很简单的,也是给我们一个二叉搜索树,然后让我们返回这个树的节点中值第K小的值,下面来分析下这道题:
- 有了上一道题的经验之后,这道题其实超级简单,因为我们已经知道了“二叉搜索树的中序遍历结果是有序的”,所以我们就可以用“ 两个全局遍历 + 中序遍历 ”就可以解决这道题
- 以上面的示例二为例,搞两个全局遍历,count = k = 3,ret = 0,ret的作用就是当count一直减减到0时,用ret标记一下当前节点的值然后返回
- 这里可以用剪枝,就是当count为0时,直接返回后面的步骤都不再执行,这就是剪枝,这一步加个判断即可
class Solution {
public:int count;int ret;int kthSmallest(TreeNode* root, int k) {count = k;dfs(root);return ret;}void dfs(TreeNode* root){if(root == nullptr || count <= 0) return;dfs(root->left);count--;if(count == 0) ret = root->val;dfs(root->right);}
};
257. 二叉树的所有路径
257. 二叉树的所有路径 - 力扣(LeetCode)

前面两道题我们提到了三个词:①全局变量 ②回溯 ③剪枝,其中回溯有个性质叫做“恢复现场”
关于恢复现场:
网上很多人在自学算法的时候,都是看到了代码中有“恢复现场”的操作,才知道这道题用了“回溯”,所以很多人以为的就是“恢复现场 --> 回溯”
而今天,我们就要把这个因果关系给反过来:
- 如果有递归,那么肯定有回溯:递归 --> 回溯
- 如果有回溯,那么肯定有“恢复现场”:回溯 --> 恢复现场
- 所以,如果有递归,那么就有恢复现场:递归 --> 恢复现场
在后面的难度较高的递归回溯题目的时候,有了“回溯 --> 恢复现场”的思路之后,写代码的过程才会有思路,并且,一旦我们在题目中用到了全局变量,那么恢复现场的操作就会显得很重要
这个题目很简单,就是让我们把每一个路径保存在一个字符串数组里面,具体步骤通过示例一和示例二很好理解,下面来分析下这道题:
- 思路很简单,就是在搜索的过程中,记录每一个节点即可,当遇到叶子节点的时候停止搜索
- 我们可以搞两个全局变量,①一个字符串数组 string[] ret:就是在我遍历时发现叶子节点时,就把结果扔 ret 里去; ②一个字符串变量 string path:作用就是在深度遍历的时候,记录一下路径,以示例一为例,遍历到1时,path的值为“1->”;当遍历到2时,path的值为“1->2>”;当遍历到5时,由于5是叶节点,所以只添加数字,所以最后path的值为“1->2->5”
- 但是前面说过,有递归就有回溯,当遍历完5,回溯到1时,接下来就是要往右边回溯,但是此时path的值不是从1开始的“1->”,而是还是之前的“1->2->5”,所以我们需要“恢复现场”,就是我们在向上返回的时候,需要不断pop掉path的结尾,但是又因为是全局path,需要很多很多次的添加和删除,不太好控制,所以这道题我们不用全局的path,后面再用
- 那么用什么代替全局path呢?我们可以在函数头设置一下:void dfs(root, path); 如果我们把path当作参数传递,那么就是每次调用path的时候,相当于重新创建了一个path变量,也就是每次深度搜索时,都用的是自己单独地path变量,代替了“恢复现场”的操作和功能
- 然后激素关心一下每一个子问题在干什么,也激素函数体的设置:如果root是叶子节点,只在path后面添加上数字即可;如果root不是叶子节点,dfs(root->left, path); dfs(root->right, path)
- 最后就是递归出口,就是注意一下剪枝:当左子树为空时,就不需要再 dfs(root->left)了,直接去搞右子树即可:if(root == nullptr) return;
class Solution {
public:vector<string> ret;vector<string> binaryTreePaths(TreeNode* root) {string path;if(root == nullptr) return ret;dfs(root, path);return ret;}void dfs(TreeNode* root, string path){path += to_string(root->val);if(root->left == nullptr && root->right == nullptr) //如果是叶子节点,直接返回,不再搜索{ret.push_back(path);return;}path += "->";if(root->left) dfs(root->left, path);if(root->right) dfs(root->right, path);}
};相关文章:
算法学习(十四)—— 二叉树的深度搜索(DFS)
目录 关于dfs 部分OJ题详解 2331. 计算布尔二叉树的值 129. 求根节点到叶节点数字之和 814. 二叉树剪枝 98. 验证二叉搜索树 230. 二叉搜索树中第K小的元素 257. 二叉树的所有路径 关于dfs 算法学习(十二)—— 递归,搜索,…...
【vue2】封装自定义的日历组件(三)之基础添加月份的加减定位到最新月份的第一天
我们在切换月份的时候,希望高亮显示在每个月的第一天上面,这样的效果我们要怎么来实现,其实也很简单,我们先看下实现的效果 实现效果 代码实现 原理就是获取到每月的第一天日期,然后再跟整个的数据进行对比ÿ…...
LabVIEW偏心圆筒流变仪测控系统
偏心圆筒流变仪是一种专门研究聚合物熔体在复杂流场中特殊流变行为的先进设备。通过结合硬件控制与LabVIEW软件开发,本系统实现了对流变仪功能的精准控制与数据采集,进一步提高了聚合物加工过程的研究精度和效率。 项目背景 传统的流变测量设备多集中于…...
Runloop
假设你的项目中有关tableView,然后还有一个定时器timer在执行,定时器代码如下: var num 0override func viewDidLoad() {super.viewDidLoad()let timer Timer(timeInterval: 1,target: self,selector: #selector(self.run),userInfo: nil,r…...
SpringBoot的Bean类三种注入方式(附带LomBok注入)
SpringBoot的Bean类三种注入方式(附带LomBok注入) 在 Spring Boot 中,Bean 的注入方式主要包括构造函数注入(Constructor Injection)、字段注入(Field Injection)以及 Setter 方法注入…...
开源向量数据库介绍说明
开源向量数据库 Milvus 特点:分布式、高性能,支持亿级向量检索。 支持的数据类型:文本、图像、音频、视频等。 使用场景:推荐系统、语义搜索、图像搜索。 数据存储后端:支持多种后端,如 SQLite、MySQL、Pos…...
【前端】深度解析 JavaScript 中的 new 关键字与构造函数
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯构造函数的核心特性💯new 关键字的执行机制💯实例代码与详细解析代码示例代码逐步解析 💯new 的内部执行模拟执行过程的详细解析 &am…...
2024年华中杯数学建模C题基于光纤传感器的平面曲线重建算法建模解题全过程文档及程序
2024年华中杯数学建模 C题 基于光纤传感器的平面曲线重建算法建模 原题再现 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号,其基本原理是当外界环境参数发生变化时,…...
使用 `typing_extensions.TypeAlias` 简化类型定义:初学者指南
使用 typing_extensions.TypeAlias 简化类型定义:初学者指南 什么是 TypeAlias?安装 typing_extensions示例代码:如何使用 TypeAlias示例 1:为简单类型定义别名示例 2:为复杂类型定义别名示例 3:结合 Union…...
如何快速批量把 PDF 转为 JPG 或其它常见图像格式?
在某些特定场景下,将 PDF 转换为 JPG 图片格式却具有不可忽视的优势。例如,当我们需要在不支持 PDF 查看的设备或软件中展示文档内容时,JPG 图片能够轻松被识别和打开;此外,对于一些网络分享或社交媒体发布的需求&…...
如何在组织中塑造和强化绩效文化?
在组织中塑造和强化绩效文化是一个系统性的工程。 一、明确绩效目标与期望 设定清晰目标 组织应根据自身战略规划,将长期目标分解为具体、可衡量、可实现、相关联、有时限(SMART)的短期和中期绩效目标。例如,一家连锁餐饮企业的…...
OllyDbg、CE简单介绍
基础知识: 想要破解软件,需要一些基础知识: 文件格式:Windows对应PE、Linux对应ELF、IOS对应Mash-0。文件格式是指操作系统规定的每个段(代码段、数据段、堆、栈)的大小、顺序等信息。 汇编语言࿱…...
Python函数——函数的返回值定义语法
一、引言 在Python中,函数的返回值是其核心功能之一,它使得函数能够将计算结果传递给调用者,进而推动程序的逻辑和功能实现。理解和掌握函数的返回值语法,不仅能够提高代码的模块化和可读性,还能使程序更加高效和灵活…...
【Pandas】pandas isna
Pandas2.2 General Top-level missing data 方法描述isna(obj)用于检测数据中的缺失值isnull(obj)用于检测数据中的缺失值notna(obj)用于检测数据中的非缺失值notnull(obj)用于检测数据中的非缺失值 pandas.isna() pandas.isna() 是 Pandas 库中的一个函数,用于…...
mysql 数据库表的大小
mysql 数据库表的大小 Mysql 查看数据库各个表占用空间 mysql如何查看数据库所有表大小 在MySQL中,要查看数据库所有表的大小,可以使用以下方法: 方法一:使用information_schema数据库 首先,通过命令行或图形界面…...
(6)JS-Clipper2之ClipperOffset
1. 描述 ClipperOffset类封装了对打开路径和关闭路径进行偏移(膨胀/收缩)的过程。 这个类取代了现在已弃用的OffsetPaths函数,该函数不太灵活。可以使用不同的偏移量(增量)多次调用Execute方法,而不必重新分配路径。现在可以在一次操作中对开放和封闭路…...
如何在Ubuntu中利用repo和git地址下载获取imx6ull的BSP
01-设置git的用户名和邮箱 git config --global user.name "suwenhao" git config --global user.email "2487872782qq.com"这里不设置的话后面在第5步的repo配置中还是会要求输入,而且以后进行相关操作都要输入,不妨现在就进行配置…...
Ruby On Rails 笔记5——常用验证下
3.Validation Options 3.1 :allow_nil 当验证值为nil时:allow_nil选项会跳过验证 class Coffee < ApplicationRecordvalidates :size, inclusion: { in: %w(small medium large),message: "%{value} is not a valid size" }, allow_nil: true end irb> Cof…...
JS听到了因果的回响
这是我学习JS的第11天了,,,我现在赶着周末学JS,然后还有二十多天就期末了呵呵呵。。。 图片切换模块 思路分析: 这是实现的代码,建议还是把不同的变量定义出来比较合适: //获取三个盒子// 小盒…...
【高中生讲机器学习】28. 集成学习之 Bagging 随机森林!
创建时间:2024-12-09 首发时间:2024-12-09 最后编辑时间:2024-12-09 作者:Geeker_LStar 嘿嘿,你好呀!我又来啦~~ 前面我们讲完了集成学习之 Boooooosting,这篇我们来看看集成学习的另一个分支…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
轻量级Docker管理工具Docker Switchboard
简介 什么是 Docker Switchboard ? Docker Switchboard 是一个轻量级的 Web 应用程序,用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器,使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
【版本控制】GitHub Desktop 入门教程与开源协作全流程解析
目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork(创建个人副本)步骤 2: Clone(克隆…...
简单介绍C++中 string与wstring
在C中,string和wstring是两种用于处理不同字符编码的字符串类型,分别基于char和wchar_t字符类型。以下是它们的详细说明和对比: 1. 基础定义 string 类型:std::string 字符类型:char(通常为8位)…...
