递归基础训练-路径总和

路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

我们可以把之前的值不断传递下去,然后到叶子节点判断是否满足条件,我们想要把之前的值一路传递下去,可以确定就是先序遍历,先对值进行处理然后传递下去,下一层可以拿到之前的所有值,然后再进行处理。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:self.ans = []def pathSum(root, path):if root is None:returnprint(root.val)if root.left is None and root.right is None:self.ans.append(sum(path)+root.val)returnpathSum(root.left, path+[root.val])pathSum(root.right, path+[root.val])pathSum(root, [])print(self.ans)for s in self.ans:if s == targetSum:return Truereturn False

终止条件:

  • 叶子节点可以作为一个终止条件,当遍历到叶子节点的时候拿到了所有的路径值,也就可以返回了root.left is None and root.right is None
  • 但是还有一种情况是节点是None,例如一个节点的左节点是None,右节点不为None,那么递归遍历左节点的时候也要停止递归并且返回,因为并不是叶子节点,所以不把结果记录下来
  • 叶子节点也可以不作为终止条件,可以作为结果的判断条件,当为叶子节点的时候把结果记录下来,继续递归

二叉树的停止条件主要就是叶子节点和None两种情况

可以引入一个外部变量来存储中间的计算结果

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:self.ans = Falsedef pathSum(root, path):if root is None:returnif root.left is None and root.right is None:if sum(path) + root.val == targetSum:self.ans = TruereturnpathSum(root.left, path+[root.val])pathSum(root.right, path+[root.val])pathSum(root,[])return self.ans

在这里插入图片描述

直接根据子问题还原出原问题,子问题:

  • 左子树是否存在某条路径的和满足target-root.val
  • 右子树是否存在某条路径的和满足target-root.val

原问题:如果左子树存在或者右子树存在,那么就存在

终止条件:

  • 子节点:如果到了子节点,那么只需要判断子节点与targetSum是否相等即可,也可以认为当前就只有一个节点
  • 节点为None,如果节点为None的话,那肯定就是不满足情况,返回False
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:if root is None:return Falseif root.left is None and root.right is None:return root.val == targetSuml = self.hasPathSum(root.left, targetSum-root.val)r = self.hasPathSum(root.right, targetSum-root.val)return l or r

路径总和 II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

要把所有搜索路径存储下来,那么必然是把之前的节点都记录下来并且传递下去,这样后续的节点才可以拿到之前的节点

路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

对于不是必须经过根节点的问题,可以将每个节点看作是根节点来搜索原问题。
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:self.ans = []# 求一个二叉树中存在路径和为targetSum的路径def everyNodePathSum(root, targetSum, path):if root is None:returnif root.left is None and root.right is None:if root.val == targetSum:self.ans.append(path + [root.val])returneveryNodePathSum(root.left, targetSum - root.val, path + [root.val])everyNodePathSum(root.right, targetSum - root.val,path + [root.val])# 遍历一个二叉树,求每个节点下存在路径为targetSum的路径def dfs(root):if root is None:returneveryNodePathSum(root,targetSum,[])dfs(root.left)dfs(root.right)dfs(root)print(self.ans)

终止条件发生了改变,不一定是到叶子节点才终止,只要在遍历的过程中存在和为targetSum的路径就存储下来

发现了一个问题,叶子节点并不是终止条件,只能说是一个判断条件,当到达叶子节点时候判断是否需要将路径加进来

[1,-1,[2,-2],[1,-1,2,-2]都是满足targetSum的路径,因此即使中间结果满足了targetSum也不能直接返回,还需要接着往下遍历
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:self.ans = []def everyNodePathSum(root, targetSum, path):if root is None:return# 不需要再叶子节点判断,每个节点都可以判断# 并且删除return,因为一个路径可能存在多种解if root.val == targetSum:self.ans.append(path + [root.val])everyNodePathSum(root.left, targetSum - root.val, path + [root.val])everyNodePathSum(root.right, targetSum - root.val,path + [root.val])def dfs(root):if root is None:returneveryNodePathSum(root,targetSum,[])dfs(root.left)dfs(root.right)dfs(root)print(self.ans)

做一个小修改来满足题解

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:self.ans = 0def everyNodePathSum(root, targetSum, path):if root is None:returnif root.val == targetSum:self.ans += 1everyNodePathSum(root.left, targetSum - root.val, path + [root.val])everyNodePathSum(root.right, targetSum - root.val,path + [root.val])def dfs(root):if root is None:returneveryNodePathSum(root,targetSum,[])dfs(root.left)dfs(root.right)dfs(root)return self.ans

二叉树的最大深度

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

根节点到叶子节点,我们同样可以把路径记录下来,当到达叶子节点的时候直接统计就可以

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def maxDepth(self, root: Optional[TreeNode]) -> int:self.ans = []def getPath(root, path):if root is None:returnif root.left is None and root.right is None:self.ans.append(path + [root.val])# 将当前节点添加到path中,并传递给左子树getPath(root.left, path + [root.val])# 将当前节点添加到path中,并传递给左子树getPath(root.right, path + [root.val])getPath(root, [])if len(self.ans) == 0:return 0return max(map(len, self.ans))

我们是求最大路径,其实并不需要将路径记录下来,只需要将路过的节点数记录下来就可以了
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def maxDepth(self, root: Optional[TreeNode]) -> int:self.ans = 0def getPath(root, depth):if root is None:returnif root.left is None and root.right is None:self.ans = max(self.ans, depth+1)# 之前的个数+1就是当前的深度,然后传递下去,左子树就是知道之前路过了几个节点getPath(root.left, depth+1)# 之前的个数+1就是当前的深度,然后传递下去,右子树就是知道之前路过了几个节点getPath(root.right, depth+1)getPath(root, 0)return self.ans

这个问题也可以直接考虑子问题
子问题:左子树的最大深度和右子树的最大深度
还原原问题:max(左子树最大深度,右子树最大)+1就是当前节点的最大深度

通常考虑后序遍历,

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def maxDepth(self, root: Optional[TreeNode]) -> int:if root is None:return 0l = self.maxDepth(root.left)r = self.maxDepth(root.right)return max(l,r)+1

二叉树的直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

最开始的想法是假如求出了左子树直径和右子树的直径,那么当前节点的直径就是左子树直径+右子树直径+1。但是会发现左子树的直径和右子树的直径并不能直接拼接成根节点的直径。
左子树的直径是[4,3,2,6],右子树的直径是[8,7,9],预期根节点的直径是[4,3,2,6]+[1]+[8,7,9],而根节点的实际直径是[4,3,2]+[1]+[7,9]。也就是说子问题的解无法合并成原问题,我们这种划分就是不对的。
在这里插入图片描述
观察后发现,根节点的直径是左子树的深度+1+右子树的深度。不只是根节点,每个节点的直径都是左子树的深度+1+右子树的深度。也就是说我们只要遍历每个节点的左右子树深度就可以了

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:self.ans = 0def depth(root):if root is None:return 0l = depth(root.left)r = depth(root.right)d = max(l,r) + 1self.ans = max(self.ans, l+r)return ddepth(root)return self.ans

这样左子树的最大深度一定是左节点一直往下到叶子节点的一条路径/,右子树的最大深度也是右节点一直往下到一条路径\,(不会出现/\这样的路径),然后加上根节点就是直径了。

二叉树中的最大路径和

路径和 是路径中各节点值的总和

这个问题也很有意思,一个简单的想法就是求出左子树的最大路径和,求出右子树的最大路径和,那么根节点的最大路径和就是root.val + max(0,左子树的路径和) + max(0,右子树的路径和),对左右子树取max的原因是路径和可以为负数,当为负数的时候就不要加在根节点上。

同样最大路径和不一定过根节点,也就是要遍历所有的节点,可以用全局变量存储最大值

ans = float('-inf')
def dfs(root):if root is None:return 0l = max(0, dfs(root.left))r = max(0, dfs(root.right))v = l + r + root.valans = max(ans,v)return v

这种写法是有问题的
在这里插入图片描述
本质还是因为左右子树的最大和路径不一定能拼接到根节点,也就是说子问题无法还原出原问题。考虑过根节点的最大路径和,也就是从根节点一路向下得到的路径和,根节点的最大路径和就是 左边路径和右边路径求出哪个大,然后跟0比较,如果小于0那么就不要加到根路径中

在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def maxPathSum(self, root: Optional[TreeNode]) -> int:self.ans = float('-inf')def dfs(root):if root is None:return 0l = max(dfs(root.left),0)r = max(dfs(root.right),0)# 过跟节点的最大路径和v = l + r + root.valself.ans = max(self.ans, v)# 返回的是根节点的最大路径return max(l,r) + root.valdfs(root)return self.ans

二叉树的直径和二叉树的最大路径本质都是在求路径,也就是要从根节点走到叶子节点,但是有两个比较难理解的地方

  1. 最终结果并不一定过根节点,任何一个节点都有可能是一个解,因此要遍历每个节点
  2. 简单的子问题无法还原出原问题的解,需要将问题转化一下,并且原始问题的解是在遍历的时候以中间结果记录下来的

总结

  1. 二叉树在递归的时候会遍历每一个节点,走完所有的路径,我们可以把之前遍历的节点信息放到path中传递下去,一直到叶子节点就拿到了所有的路径
def dfs(root, path):# 如果为None就直接返回,比如一个节点左孩子是None,右孩子非空# dfs(None), dfs(root.right)if root is None:return# 搜索路径就是根节点到叶子节点,直接打印路径if root.left is None and root.right is None:print(path)# 可以return也可以不return,因为根节点的左右子树都是None# 继续往下执行就是左子树dfs(None),右子树dfs(None)# returndfs(root.left, path + [root.val])dfs(root.right, path + [root.val])
  1. 每次在传递path的时候,其实发生了一次拷贝,递归左子树,会拷贝一份path传下去,递归右子树,同样拷贝一份path传递下去。左子树如何修改path都不会影响右子树的path

在这里插入图片描述
3. 二叉树明明只是遍历了一遍节点为什么可以得到所有的路径呢?一开始我的想法是先序遍历到叶子节点,停止后再回溯遍历右子树,然后再网上回溯。这种想法其实是不太完善。从递归树上可以知道,左右子树其实都执行了dfs,然后子树的子树也都执行了dfs,在递进的时候其实每个节点就已经都执行了处理
对于节点7和节点8,都拿到了之前的路径,

def dfs(5,[1,2]):dfs(7,[1,2,5])dfs(8,[1,2,5])

可以看到并不需要所谓的回溯,节点7拿到了之前所有的路径,并执行dfs记录了一条路径,而节点8也执行了dfs,拿到之前的路几个并记录了一条路径。也可以认为7和8是同时遍历的

def dfs(5,[1,2]):thread(dfs(7,[1,2,5])) thread(dfs(8,[1,2,5]))
def dfs(root):if root is None:returnbefore_process()dfs(root.left)dfs(root.right)after_process()

说明白了每个节点都会执行before_process和after_process,区别在于before_process是递归之前调用的,我们先对数据进行处理,然后再递归,处理完成的数据可以继续传递下去,例如path,而after_process是归的时候执行的,处理完成的结果逐步向上返回
先序遍历

max_depth = 0
def depth(root,d=0):# 节点为空就直接返回,无需继续执行if root is None:return# 当前节点不为None,路径长度+1d += 1 # 之前经历过了多少个节点max_depth = max(max_depth, d)depth(root.left, d)  # 处理好的数据传递下去depth(root.right, d) # 处理好的数据传递下去

后序遍历

def depth(root):# 节点为空就直接返回,无需继续执行if root is None:return 0l = depth(root.left)  # 处理好的数据传递下去r = depth(root.right) # 处理好的数据传递下去d = max(l,r) # 后序处理完成后向上返回return d

后序遍历就是把小的子问题逐步还原出原问题的结果。

二叉树直径:从某个节点到叶子节点的最长路径,其实就是最大深度

def depth(root):if root is None:return 0l = depth(root.left)r = depth(root.right)d = max(l,r)return d

最大深度还是比较好理解的,一路递归下去(到叶子节点)如果碰到None则返回0,在遍历的过程中其实会记录所有的路径,我们在这些路径中选择一条最长的也就是最大深度

def depth(root):if root is None:return 0l = depth(root.left)r = depth(root.right)d = max(l,r)return d

在这里插入图片描述

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

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

相关文章

docker在基础镜像上,比如rockylinux,如何配置yum仓库

在基础镜像rockylinux上 启动的容器,没有yum仓库,就执行不了一些命令 ~]docker run -itd --name linux rockylinux:8.5~]# docker exec -it linux bash /]# ifconfig bash: ifconfig: command not found/]# vim bash: vim: command not found …

计算机组成原理(笔记4)

定点加减法运算 补码加法&#xff1a; 补码减法&#xff1a; 求补公式&#xff1a; 溢出的概念 在定点小数机器中,数的表示范围为|&#xff58;|<1。在运算过程中如出现大于1的现象,称为 “溢出”。 上溢&#xff1a;两个正数相加&#xff0c;结果大于机器所能表示的最…

20240923软考架构-------软考186-190答案解析

每日打卡题186-190答案 186、Mesh 化架构是把&#xff08; &#xff09;从业务进程中分离&#xff0c;分离后在业务进程中只保留很“薄”的Client部分&#xff0c;Client 通常很少变化&#xff0c;只负责与 Mesh进程通信&#xff0c;原来需要在SDK中处理的流量控制、安全等逻辑…

数据结构-线性表的单链式存储结构图解及C语言实现

概念 链式存储&#xff1a;结点在存储器中的位置是任意的&#xff0c;即逻辑相邻的数据元素在物理上不一定相邻 链式存储结构也称非顺序映像或链式映像 图解 链式存储结构中结点一般有两个部分组成&#xff0c;即数据域(data)和指针域&#xff0c;数据域是用于存放数据的&…

开源ids snort (windows版)

Snort-IPS-on-Windows-main资源-CSDN文库 GitHub - eldoktor1/Snort-IPS-on-Windows: A comprehensive guide to installing and configuring Snort IPS on Windows, ensuring robust network security 手动打造Snortbarnyard2BASE可视化告警平台 - FreeBuf网络安全行业门户 …

JavaWeb--小白笔记07:servlet对表单数据的简单处理

这里的servlet对表单数据的处理是指使用IDEA创建web工程&#xff0c;再创建html和class文件进行连接&#xff0c;实现html创建一个表单网页&#xff0c;我们对网页中的表单进行填充&#xff0c;可以通过class文件得到网页我们填充的内容进行打印到控制台。 一登录系统页面---h…

Linux网络之UDP与TCP协议详解

文章目录 UDP协议UDP协议数据报报头 TCP协议确认应答缓冲区 超时重传三次握手其他问题 四次挥手滑动窗口流量控制拥塞控制 UDP协议 前面我们只是说了UDP协议的用法,但是并没有涉及到UDP协议的原理 毕竟知道冰箱的用法和知道冰箱的原理是两个层级的事情 我们首先知道计算机网…

1. BOOT.BIN 2. 固化 3. 启动 4. SDK 5. 文件

在进行FPGA的开发与固化过程中&#xff0c;生成BOOT.BIN文件是一个重要的步骤。BOOT.BIN文件通常包含了系统启动所需的不同文件&#xff0c;以下是如何创建和使用该文件的详细说明。 ### 生成BOOT.BIN文件的步骤 1. **方法一&#xff1a;通过项目构建** - 右键单击项目&#xf…

Android Camera 预览角度和拍照保存图片角度相关

–基于Android R(11) 关于Camera Camera Framework 的架构 Android Camera Framework 是一个分层架构&#xff0c;由以下组件组成&#xff1a; HAL&#xff08;硬件抽象层&#xff09;: HAL 抽象底层相机硬件,提供与不同设备相机进行交互的标准接口.CameraService : Camera…

怎么用gitee做一个图片仓库,在md文档中用这个图片网络地址,然后显示图片

痛因&#xff1a;我为什么要这样做&#xff0c;呃&#xff0c;我一开始图片都是存本地地址的&#xff0c;放在和这个md文档同级的assets文件夹下面&#xff0c;这样子确实当时很方便&#xff0c;复制粘贴什么也不用管&#xff0c;但是想把这个文档分享给别的人的时候&#xff0…

【软考】计算机系统硬件基本组成

目录 一、说明 一、说明 1. 计算机系统是由硬件和软件组成的&#xff0c;它们协同工作来运行程序。 2. 计算机的基本硬件系统由运算器、控制器、存储器、输入设备和输出设备5大部件组成。 3. 运算器、控制器等部件被集成在一起统称为中央处理单元(Central Processing Unit&…

美信监控易的优势:长期稳定运行

美信监控易作为一款运维产品&#xff0c;其显著的优势在于能够长期稳定运行。在IT运维领域&#xff0c;系统的稳定性是至关重要的&#xff0c;它直接关系到企业的业务连续性和客户满意度。美信监控易通过其自研的数据库和先进的监测技术&#xff0c;确保了系统的高可用性&#…

Frida-JSAPI:Interceptor使用

拦截器 Interceptor.attach(target, callbacks[, data]) 参数分析 target &#xff1a;target是一个NativePointer&#xff0c;用于指定想要拦截的函数的地址。callbacks &#xff1a;参数是一个包含一个或多个回调函数的对象。 onEnter(args) 回调函数&#xff0c;接收一个参…

Python习题 199:统计重复最多的随机数字

(编码题)用 Python 随机生成 99 个 1 到 100 的数字,统计重复数量最多的前 5 个数字。 from collections import Counter import randomnums = random.choices(range(1, 100), k=99

Qt快捷键说明与用法

编辑与查找 CtrlF&#xff1a;在当前编辑窗口中查找关键字。支持大小写相关、全词匹配、正则表达式匹配等选项&#xff0c;并且查找之后还可以进行替换操作。 CtrlShiftF&#xff1a;进行全局查找&#xff0c;不局限于当前文件。注意&#xff0c;在某些情况下&#xff0c;这个…

AWS EKS 中的负载均衡和 TLS 配置:全面指南

在现代云原生应用程序架构中,负载均衡器扮演着至关重要的角色。对于运行在 Amazon Elastic Kubernetes Service (EKS) 上的应用程序来说,理解和正确配置负载均衡是确保应用程序高可用性、可扩展性和安全性的关键。本文将全面介绍 AWS EKS 中的负载均衡配置,包括 Application…

Spring 的循环依赖

在 Spring 中&#xff0c;循环依赖是指两个或多个 Bean 相互依赖&#xff0c;导致在创建过程中出现了依赖死锁的问题。为了解决循环依赖&#xff0c;Spring 引入了三级缓存机制。了解为什么需要三级缓存机制&#xff0c;首先要明白循环依赖是如何发生的&#xff0c;以及两级缓存…

HarmonyOS鸿蒙开发实战(5.0)悬浮窗拖拽和吸附动画实践

鸿蒙HarmonyOS NEXT开发实战往期文章必看&#xff08;持续更新......&#xff09; HarmonyOS NEXT应用开发性能实践总结 HarmonyOS NEXT应用开发案例实践总结合集 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【Perf调测】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 基本概念 Perf为性能分析工具&#xff0c;依赖PMU&#xff08;Per…

代码随想录打卡Day41

最近事情好多。。全堆一块了&#xff0c;今天先写两题吧&#xff0c;剩下一题明天解决。 121. 买卖股票的最佳时机 这道题纯不会&#xff0c;不知道该怎么构造dp数组&#xff0c;更不知道dp数组的含义&#xff0c;看完讲解以后感觉这样的dp数组构造还挺巧妙的&#xff0c;第一…