dfs二叉树中的深搜(回溯、剪枝)--力扣129、814、230、257

目录

1.1题目链接:129.求根节点到叶结点数字之和

1.2题目描述:给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。

1.3解法(dfs-前序遍历):

2.1题目链接:814.二叉树剪枝

2.2题目描述:

2.3解法(dfs-后序遍历):

3.1题目链接:98.验证二叉搜索树

3.2题目描述:

3.3解法(利用中序遍历)

4.1题目链接:230.二叉搜索树中第K小的元素

4.2题目描述:

4.3解法(中序遍历+计数器剪枝)

5.1题目链接:257.二叉树的所有路径

5.2题目描述:

5.3解法(回溯):


本文提供了四道关于二叉树的问题及它们的解法:

  1. 求根节点到叶结点数字之和:给定一个二叉树,树中每个节点存放一个0到9之间的数字,求从根节点到叶节点生成的所有数字之和。解法使用深度优先搜索(DFS)的前序遍历,整合父节点信息与当前节点信息计算当前节点数字,并递归向下传递,在叶子节点处返回结果。

  2. 二叉树剪枝:给定一个二叉树,节点值为0或1,返回移除所有不包含1的子树的原二叉树。解法使用DFS的后序遍历,逐步删除叶子节点,保证删除后节点仍满足条件。

  3. 验证二叉搜索树:判断给定二叉树是否是有效的二叉搜索树。解法利用中序遍历,递归判断左子树是否为BST,当前节点是否满足BST条件,以及右子树是否为BST。

  4. 二叉搜索树中第K小的元素:在二叉搜索树中查找第K小的元素。解法使用中序遍历配合计数器进行剪枝,当计数器等于0时找到第K小的元素。

此外,还提供了第5道题目二叉树的所有路径,返回所有从根节点到叶子节点的路径的解法,使用回溯法将路径存储在结果中。

1.1题目链接:129.求根节点到叶结点数字之和

1.2题目描述:
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。

每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。

计算从根节点到叶节点生成的 所有数字之和 。

叶节点 是指没有子节点的节点。

示例 1:

输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25

示例 2:

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026

提示:

  • 树中节点的数目在范围 [1, 1000] 内
  • 0 <= Node.val <= 9
  • 树的深度不超过 10

1.3解法(dfs-前序遍历):

前序遍历按照根结点、左子树、右子树的顺序遍历二叉树的所有结点,通常用于子节点的状态依赖于父节点状态的题目。

算法思路:

在前序遍历的过程中,我们可以往左右子树传递信息,并且在回溯时得到左右子树的返回值。递归函数可以帮我们完成两件事:

  1. 将父节点的数字与当前结点的信息整合到一起,计算出当前结点的数字,然后传递到下一层进行递归;
  2. 当遇到叶子结点的时候,就不再向下传递信息,而是将整合的结果向上一直回溯到根节点。

当递归结束时,根结点需要返回的值也就被更新为了整棵数的数字和。

算法流程
递归函数设计:int dfs(TreeNode* root, int num)

  1. 返回值:当前子树计算的结果(数字和);
  2. 参数num:递归过程中往下传递的信息(父结点的数字)
  3. 函数作用:整合父节点的信息与当前结点的信息计算当前结点数字,并向下传递,在回溯时返回当前子树(当前结点作为子树根结点)数字和

递归函数流程

  1. 当遇到空节点的时候,说明这条路从根节点开始没有分支,返回0
  2. 结合父节点传下的信息以及当前节点的val,计算出当前节点数字sum
  3. 如果当前节点是叶子节点,直接返回整合后的结果sum
  4. 如果当前节点不是叶子节点,将sum传到左右子树中去,得到左右子树中节点路径的数字和,然后相加返回结果。
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int sumNumbers(TreeNode* root) {return dfs(root,0);}int dfs(TreeNode* root, int sum){sum = sum*10 + root->val;if(root->left == nullptr && root->right == nullptr){return sum;}int ret = 0;if(root->left)ret += dfs(root->left, sum);if(root->right)ret += dfs(root->right, sum);return ret;}
};

2.1题目链接:814.二叉树剪枝

2.2题目描述:

给你二叉树的根结点 root ,此外树的每个结点的值要么是 0 ,要么是 1 。

返回移除了所有不包含 1 的子树的原二叉树。

节点 node 的子树为 node 本身加上所有 node 的后代。

示例 1:

输入:root = [1,null,0,0,1]
输出:[1,null,0,null,1]
解释:
只有红色节点满足条件“所有不包含 1 的子树”。 右图为返回的答案。

示例 2:

输入:root = [1,0,1,0,0,0,1]
输出:[1,null,1,null,1]

示例 3:

输入:root = [1,1,0,1,1,0,1,0]
输出:[1,1,0,1,1,null,1]

提示:

  • 树中节点的数目在范围 [1, 200] 内
  • Node.val 为 0 或 1

2.3解法(dfs-后序遍历):

后序遍历按照左子树、右子树、根节点的顺序遍历二叉树的所有结点,通常用于父节点的状态依赖于子结点状态的题目。

算法思路

如果我们选择从上往下删除,我们需要收集左右子树的信息,这可能导致代码编写相对困难。然而,通过观察我们可以发现,如果我们先删除最底部的叶子结点,然后再处理删除后的结点,最终的结果并不会受到影响。

因此,我们可以采用后序遍历的方式来解决这个问题。在后序遍历中,我们先处理左子树,然后处理右子树,最后再处理当前节点。在处理当前节点时,我们可以判断其是否为叶子节点且其值是否为0,如果满足条件,我们可以删除当前节点。

  • 需要注意的是,在删除叶子节点时,其父节点很可能会成为新的叶子节点。因此,在处理完子节点后,我们仍然需要处理当前节点。这也是为什么选择后序遍历的原因(后序遍历首先遍历到的一定是叶子节点)。
  • 通过使用后序遍历,我们可以逐步删除叶子节点,并且保证删除后的节点仍然满足删除操作的要求。这样,我们可以较为方便地实现删除操作,而不会影响最终的结果。
  • 若在处理结束后所有叶子节点的值均为1,则所有子树均包含1,此时可以返回。

算法流程:

递归函数设计:void dfs(TreeNode* root)

  1. 返回值:无;
  2. 参数:当前需要处理的节点
  3. 函数作用:判断当前节点是否需要删除,若需要删除,则删除当前节点。
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* pruneTree(TreeNode* root) {if(root == nullptr) return nullptr;root->left = pruneTree(root->left);root->right = pruneTree(root->right);if(root->left == nullptr && root->right == nullptr && root->val == 0){delete root;//防止内存泄漏root = nullptr;}return root;}
};

3.1题目链接:98.验证二叉搜索树

3.2题目描述:

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:root = [2,1,3]
输出:true

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

提示:

  • 树中节点数目范围在[1, 10^4] 内
  • -2^31 <= Node.val <= 2^31 - 1

3.3解法(利用中序遍历)

后序遍历按照左子树、根节点、右子树的顺序遍历二叉树的所有节点,通常用于二叉搜索树相关题目。
算法思路
如果一棵树是二叉搜索树,那么它的中序遍历的结果一定是一个严格递增的序列。
因此,我们可以初始化一个无穷小的全区变量,用来记录中序遍历过程中的前驱结点。那么就可以在中序遍历的过程中,先判断是否和前驱结点构成递增序列,然后修改前驱结点为当前结点,传入下一层的递归中。
算法流程
1. 初始化一个全局的变量 prev,用来记录中序遍历过程中的前驱结点的val;
2. 中序遍历的递归函数中:

  • 设置递归出口:root == nullptr的时候,返回true;
  • 先递归判断左子树是否是二叉搜索树,用left标记
  • 然后判断当前节点是否满足二叉搜索树,用cur标记                                                               如果当前节点的val大于prev,说明满足条件,cur改为true                                                 如果当前节点的val小于等于prev,说明不满足条件,cur改为false                              
  • 最后递归判断右子树是否是二叉搜索树,用right标记

3.只有当left,cur,right都是true的时候,才返回true

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int prev;bool isValidBST(TreeNode* root) {if(root == nullptr)return true;bool left = isValidBST(root->left);//剪枝if(left == false)return false;bool cur = false;if(root->val > prev)cur = true;//剪枝if(cur == false)return false;prev = root->val; bool right = isValidBST(root->right);return left&&right&&cur;}
};

4.1题目链接:230.二叉搜索树中第K小的元素

4.2题目描述:

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。

示例 1:

输入:root = [3,1,4,null,2], k = 1
输出:1

示例 2:

输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3

提示:

  • 树中的节点数为 n 。
  • 1 <= k <= n <= 10^4
  • 0 <= Node.val <= 10^4

4.3解法(中序遍历+计数器剪枝)

算法思路
我们可以根据中序遍历的过程,只需扫描前k个结点即可。因此,我们可以创建一个全局的计数器count,将其初始化为k,每遍历一个节点就将count--。直到某次递归的时候,count 的值等于 1,说明此时的结点就是我们要找的结果。

算法流程
1. 定义一个全局的变量count,在主函数中初始化为k的值(不用全局也可以,当成参数传入递归过程中);

递归函数的设计:int dfs(TreeNode* root):
• 返回值为第k个结点;


递归函数流程(中序遍历):
1. 递归出口:空节点直接返回-1,说明没有找到;
2. 去左子树上查找结果,记为 left:
        a. 如果 left == -1,说明没找到,继续执行下面逻辑;
        b. 如果 left != -1,说明找到了,直接返回结果,无需执行下面代码(剪枝)
3. 如果左子树没找到,判断当前结点是否符合:
        a. 如果符合,直接返回结果
4.如果当前结点不符合,去右子树上寻找结果

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int count;int ret = 1;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);}
};

5.1题目链接:257.二叉树的所有路径

5.2题目描述:

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

输入:root = [1]
输出:["1"]

提示:

  • 树中节点的数目在范围 [1, 100] 内
  • -100 <= Node.val <= 100

5.3解法(回溯):

算法思路:
使用深度优先遍历(DFS)求解。
路径以字符串形式存储,从根节点开始遍历,每次遍历时将当前节点的值加入到路径中,如果该节点为叶子节点,将路径存储到结果中。否则,将"->”加入到路径中并递归遍历该节点的左右子树。
定义一个结果数组,进行递归。递归具体实现方法如下:
        1. 如果当前节点不为空,就将当前节点的值加入路径 path 中,否则直接返回;
        2. 判断当前节点是否为叶子节点,如果是,则将当前路径加入到所有路径的存储数组 paths 中;
        3. 否则,将当前节点值加上“->"作为路径的分隔符,继续递归遍历当前节点的左右子节点。
        4. 返回结果数组。


         特别地,我们可以只使用一个字符串存储每个状态的字符串,在递归回溯的过程中,需要将路径中的当前节点移除,以回到上一个节点。


具体实现方法如下:
1.定义一个结果数组和一个路径数组。
2. 从根节点开始递归,递归函数的参数为当前节点、结果数组和路径数组。
        a. 如果当前节点为空,返回
        b. 将当前节点的值加入到路径数组中。
        c.如果当前节点为叶子节点,将路径数组中的所有元素拼接成字符串,并将该字符串存储到结果数组中。
        d. 递归遍历当前节点的左子树。
        e. 递归遍历当前节点的右子树。
        f.回溯,将路径数组中的最后一个元素移除。以返回到上一个节点

3.返回结果数组

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<string> ret;vector<string> binaryTreePaths(TreeNode* root) {dfs(root,"");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);}
};

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

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

相关文章

【树形dp题解】dfs的巧妙应用

【树形dp题解】dfs的巧妙应用 [P2986 USACO10MAR] Great Cow Gathering G - 洛谷 题目大意&#xff1a; Bessie 正在计划一年一度的奶牛大集会&#xff0c;来自全国各地的奶牛将来参加这一次集会。当然&#xff0c;她会选择最方便的地点来举办这次集会。 每个奶牛居住在 N N …

【c++深入系列】:new和delete运算符详解

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; “生活不会向你许诺什么&#xff0c;尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去&#xff0c;终有一天&…

Spring Boot 实现防盗链

在 Spring Boot 项目中实现防盗链可以通过多种方式&#xff0c;下面为你介绍两种常见的实现方法&#xff0c;分别是基于请求头 Referer 和基于令牌&#xff08;Token&#xff09;的防盗链。 基于请求头 Referer 的防盗链 这种方法通过检查请求头中的 Referer 字段&#xff0c…

悄悄话识别、 打电话识别、攀高识别三种识别算法

在摄像头正对场景下,悄悄话识别(唇语识别)、打电话识别和攀高识别是三种典型的行为检测技术。以下从技术原理、算法模型、应用场景及挑战等方面进行详细分析: 一、悄悄话识别(唇语识别) 技术原理 唇语识别通过分析嘴唇的几何特征(形状、开合程度、运动轨迹)和动态变化…

centos部署的openstack发布windows虚拟机

‌CentOS上部署的OpenStack可以发布Windows虚拟机‌。在CentOS上部署OpenStack后&#xff0c;可以通过OpenStack平台创建和管理Windows虚拟机。以下是具体的步骤和注意事项&#xff1a; ‌安装和配置OpenStack‌&#xff1a; 首先&#xff0c;确保系统满足OpenStack的最低硬件…

【电子通识】案例:电缆的安装方式也会影响设备的可靠性?

背景 在日常生活中&#xff0c;我们常常会忽略一些看似微不足道的细节&#xff0c;但这些细节有时却能决定设备的寿命和安全性。比如&#xff0c;你知道吗&#xff1f;一根电缆的布置方式&#xff0c;可能会决定你的设备是否会因为冷凝水而损坏。 今天&#xff0c;我们就来聊聊…

【Web APIs】JavaScript 操作多个元素 ④ ( 表格全选复选框案例 )

文章目录 一、核心要点解析 - 表格全选复选框案例1、案例需求2、复选框设置3、获取 全选复选框 和 普通复选框4、设置 全选复选框 逻辑5、设置 普通复选框 逻辑 二、完整代码示例1、代码示例2、执行结果 一、核心要点解析 - 表格全选复选框案例 1、案例需求 在表格中 , 设置 多…

OpenAI发布GPT-4.1系列模型——开发者可免费使用

OpenAI刚刚推出GPT-4.1模型家族&#xff0c;包含GPT-4.1、GPT-4.1 Mini和GPT-4.1 Nano三款模型。重点是——现在全部免费开放&#xff01; 虽然技术升级值得关注&#xff0c;但真正具有变革意义的是开发者能通过Cursor、Windsurf和GitHub Copilot等平台立即免费调用这些模型。…

《重构全球贸易体系用户指南》解读

文章目录 背景核心矛盾与理论框架美元的“特里芬难题”核心矛盾目标理论框架 政策工具箱的协同运作机制关税体系的精准打击汇率政策的混合干预安全工具的复合运用 实施路径与全球秩序重构阶段性目标 风险传导与反制效应内部失衡加剧外部反制升级系统性风险 范式突破与理论再思考…

磁盘清理-C盘

0.采用的工具——WizTree&#xff08;一定要以管理员身份运行&#xff09; 没有以管理员身份运行时&#xff1a; 以管理员身份运行&#xff1a;&#xff08;查出很多之前没有查出的文件&#xff09; 1.该死的优酷&#xff01;缓存占我11个G的内存 2.C 盘 Dell 文件夹下的 SARe…

锚定“体验驱动”,锐捷EDN让园区网络“以人为本”

作者 | 曾响铃 文 | 响铃说 传统的网络升级路径&#xff0c;一如巴别塔的建造思路一般——工程师们按技术蓝图逐层堆砌&#xff0c;却常与地面用户的实际需求渐行渐远&#xff0c;从而带来了诸多体验痛点&#xff0c;如手工配置效率低下、关键业务用网无法保障、网络架构趋于…

pid_t

用最简单的方式解释&#xff1a; pid_t 就像是一个"专门用来装进程号码的盒子"。 实际本质&#xff1a; 这个盒子里面装的是整数&#xff08;就像 int&#xff09;但给它贴了专用标签&#xff0c;标明"只能装进程ID" 为什么不用普通int&#xff1a; 就像…

如何处理Python爬取视频时的反爬机制?

文章目录 前言1. IP 封禁2. 验证码3. 用户代理&#xff08;User-Agent&#xff09;检测4. 动态内容加载5. 加密和签名验证 前言 在使用 Python 爬取视频时&#xff0c;网站可能会设置多种反爬机制来阻止爬虫&#xff0c;下面为你介绍一些常见反爬机制及对应的处理方法&#xf…

如何利用GM DC Monitor快速监控一台网络类设备

GM DC Monitor v2.0在网络类设备监控的效率非常高&#xff01; 如果您需要管理运维大量的网络类设备&#xff0c;GM DC Monitor是个不错的选择。 如果您具备一定的采集脚本编写能力&#xff0c;可以在平台的定制属于自己的监控模板&#xff01; 1&#xff09;首先建立数据中…

特殊文件以及日志——特殊文件

一、特殊文件 必要性&#xff1a;可以用于存储多个用户的&#xff1a;用户名、密码。这些有关系的数据都可以用特殊文件来存储&#xff0c;然后作为信息进行传输。 1. 属性文件.properties&#xff08;键值对&#xff09; &#xff08;1&#xff09;特点&#xff1a; 都只能…

基于AD9767高速DAC的DDS信号发生器

DDS信号发生器原理 DDS控制信号发生原理图 DDS主要由相位累加器、相位调制器、波形数据表以及D/A转换器构成。其中相位累加器由N位加法器与N位寄存器构成。每个时钟周期的时钟上升沿,加法器就将频率控制字与累加寄存器输出的相位数据相加,相加的结果又反馈至累加寄存…

镜像端口及观察端口的配置

配好路由器的各个接口的IP PC1ping PC3的IP&#xff0c;在路由器中抓2/0/0端口的包&#xff0c;可观察到无结果 输入observe-port interface g 2/0/0 命令配置观察端口 输入mirror to observe-port both命令 &#xff08;其中both表示接收来去的数据包&#xff0c;inboun…

K8S_ResourceQuota与LimitRange的作用

ResourceQuota 作用详解 资源总量控制&#xff1a;ResourceQuota能对命名空间内的资源使用总量进行限制。在一个Kubernetes集群中&#xff0c;存在多个命名空间&#xff0c;每个命名空间可看作一个独立的工作单元。通过设置ResourceQuota&#xff0c;可以防止某个命名空间过度…

Redis之缓存击穿

Redis之缓存击穿 文章目录 Redis之缓存击穿一、什么是缓存击穿二、缓存击穿常见解决方案1. 互斥锁&#xff08;Mutex Lock&#xff09;2. 永不过期 后台刷新3. 逻辑过期&#xff08;异步更新&#xff09; 三、案例1.基于互斥锁解决缓存击穿2.基于逻辑过期解决缓存击穿 四、注意…

Spring Boot 中使用 Netty

2025/4/15 向 一、什么是Netty Netty 是 Java 中一个非常高性能的网络通信框架&#xff0c;用来开发服务器和客户端程序&#xff0c;主要用于处理 TCP/UDP 的网络连接&#xff0c;比如&#xff1a; 聊天服务 实时推送 高并发网络通信&#xff08;比如游戏、IoT、金融系统&a…