递归基础训练-路径总和

路径总和

给你二叉树的根节点 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,一经查实,立即删除!

相关文章

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

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

数据结构-线性表的单链式存储结构图解及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协议的原理 毕竟知道冰箱的用法和知道冰箱的原理是两个层级的事情 我们首先知道计算机网…

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

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

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

美信监控易作为一款运维产品&#xff0c;其显著的优势在于能够长期稳定运行。在IT运维领域&#xff0c;系统的稳定性是至关重要的&#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…

Qt --- 常用控件的介绍 --- 其他控件

一、QPushButton QWidget中设计到的各种属性/函数/使用方法&#xff0c;针对接下来要介绍的Qt的各种控件都是有效的。 使用QPushButton表示一个按钮&#xff0c;这也是当前我们最熟悉的一个控件了。这个类继承了QAbstractButton&#xff0c;这个类是一个抽象类&#xff0c;是…

C++自动驾驶面试核心问题整理

应用开发 概述&#xff1a;比较基础&#xff0c;没啥壁垒&#xff0c;主要有linux开发经验即可 问题&#xff1a;基础八股&#xff0c;如计算机网络、操作系统、c11等基础三件套&#xff1b;中等难度算法题1-2道。 中间件开发&#xff08;性能优化&#xff09; 概述&am…

Set 和 Map 的模拟实现

1、引言 在数据结构与算法的学习与实践中&#xff0c;关联容器&#xff08;associative containers&#xff09;是不可忽视的重要工具。作为高效管理数据的一类容器&#xff0c;C 标准库中的 set 和 map 在现代软件开发中扮演着关键角色。这两个容器通过平衡二叉搜索树&#x…

【通讯协议】S32K142芯片——LIN通信的学习和配置

文章目录 前言1.LIN是什么&#xff1f;2. LIN连接结构及节点构成3. 帧的组成3.1 帧头3.1.1 同步间隔场&#xff08;Break&#xff09;3.1.2 同步场&#xff08;Synch&#xff09;3.1.3 标识符场&#xff08;PID&#xff09; 3.2 帧响应3.2.1 数据场3.2.2 校验和场 3. 代码配置总…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.6 处理器架构2 函数

新的架构来了&#xff0c;本游戏的最后一个攻略分享&#xff0c;最后汇编部分无非是对于操作码的熟练&#xff0c;硬件没有问题&#xff0c;那么也就无关痛痒了。 汇编实现&#xff0c;两数相或和两数相与非一起相与即可。 八位异或器&#xff0c;整就完事了。 有手就行。 利…

干货满满:嵌入式电阻的重要作用全知晓

在嵌入式开发中&#xff0c;有一个小小的元件&#xff0c;它看似不起眼&#xff0c;却在电路中扮演着极其重要的角色。它就是——电阻。很多初学者认为电阻只是用来“分压降流”&#xff0c;但其实&#xff0c;电阻的作用远比我们想象的要复杂和关键。今天&#xff0c;我们就来…

LeetCode 2374.边积分最高的节点:模拟

【LetMeFly】2374.边积分最高的节点&#xff1a;模拟 力扣题目链接&#xff1a;https://leetcode.cn/problems/node-with-highest-edge-score/ 给你一个有向图&#xff0c;图中有 n 个节点&#xff0c;节点编号从 0 到 n - 1 &#xff0c;其中每个节点都 恰有一条 出边。 图…

思科安全网络解决方案

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

【门牌制作 / A】

题目 代码 #include <bits/stdc.h> using namespace std; int main() {int cnt 0;for (int i 1; i < 2020; i){string s;s to_string(i);cnt count(s.begin(), s.end(), 2);}cout << cnt; }

【C++篇】走进C++标准模板库:STL的奥秘与编程效率提升之道

文章目录 C STL 初探&#xff1a;打开标准模板库的大门前言第一章: 什么是STL&#xff1f;1.1 标准模板库简介1.2 STL的历史背景1.3 STL的组成 第二章: STL的版本与演进2.1 不同的STL版本2.2 STL的影响与重要性 第三章: 为什么学习 STL&#xff1f;3.1 从手动编写到标准化解决方…

【论文速看】DL最新进展20240923-长尾综述、人脸防伪、图像分割

目录 【长尾学习】【人脸防伪】【图像分割】 【长尾学习】 [2024综述] A Systematic Review on Long-Tailed Learning 论文链接&#xff1a;https://arxiv.org/pdf/2408.00483 长尾数据是一种特殊类型的多类不平衡数据&#xff0c;其中包含大量少数/尾部类别&#xff0c;这些类…