递归基础训练-路径总和

news2024/12/28 3:34:13

路径总和

给你二叉树的根节点 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:
                return
            print(root.val)
            if root.left is None and root.right is None:
                self.ans.append(sum(path)+root.val)
                return
            pathSum(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 True
        return 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 = False
        def pathSum(root, path):
            if root is None:
                return
            if root.left is None and root.right is None:
                if sum(path) + root.val == targetSum:
                    self.ans = True
                return

            pathSum(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 False
        if root.left is None and root.right is None:
            return root.val == targetSum
        
        l = 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:
                return
            
            if root.left is None and root.right is None:
                if root.val == targetSum:
                    self.ans.append(path + [root.val])
                return
            
            everyNodePathSum(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:
                return
            everyNodePathSum(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:
                return
            everyNodePathSum(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 = 0

        def everyNodePathSum(root, targetSum, path):
            if root is None:
                return
            
            if root.val == targetSum:
                self.ans += 1
            
            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:
                return
            everyNodePathSum(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:
                return
            
            if 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 0
        return 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 = 0
        def getPath(root, depth):
            if root is None:
                return

            if 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 0
        
        l = 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 = 0
        def depth(root):
            if root is None:
                return 0
            
            l = depth(root.left)
            r = depth(root.right)
            d = max(l,r) + 1
            self.ans = max(self.ans, l+r)
            return d
        depth(root)
        return self.ans

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

二叉树中的最大路径和

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

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

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

ans = float('-inf')
def dfs(root):
	if root is None:
		return 0
	
	l = max(0, dfs(root.left))
	r = max(0, dfs(root.right))
	
	v = l + r + root.val
	ans = 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 0
            
            l = max(dfs(root.left),0)
            r = max(dfs(root.right),0)
            # 过跟节点的最大路径和
            v = l + r + root.val
            self.ans = max(self.ans, v)
            # 返回的是根节点的最大路径
            return max(l,r) + root.val
        
        dfs(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)
		# return
	dfs(root.left, path + [root.val])
	dfs(root.right, path + [root.val])
	
  1. 每次在传递path的时候,其实发生了一次拷贝,递归左子树,会拷贝一份path传下去,递归右子树,同样拷贝一份path传递下去。左子树如何修改path都不会影响右子树的path

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

def dfs(root):
	if root is None:
		return
	before_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,路径长度+1
	d += 1 # 之前经历过了多少个节点
	max_depth = max(max_depth, d)
	depth(root.left, d)  # 处理好的数据传递下去
	depth(root.right, d) # 处理好的数据传递下去

后序遍历

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

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

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

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

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

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

在这里插入图片描述

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

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

相关文章

【图虫创意-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…

R语言统计分析——散点图2(散点图矩阵、高密度散点图)

参考资料:R语言实战【第2版】 1、散点图矩阵 pairs()函数可以创建基础的散点图矩阵。下面代码用于绘制一个散点图矩阵,包含mtcars数据集中的mpg、disp、drat和wt四个变量: pairs(~mpgdispdratwt,datamtcars,main"Basic Scatter Plot M…

输煤传送带异物识别检测数据集 yolo数据集 2400张

输煤传送带异物识别检测数据集 yolo数据集 2400张 输煤传送带异物识别检测数据集介绍 数据集名称 输煤传送带异物识别检测数据集(Conveyor Belt Foreign Object Detection Dataset) 数据集概述 该数据集专为输煤传送带上的异物识别检测设计&#xff0…

Unity携程Coroutine用法

一.携程概述 官方的解释是,携程允许你可以在多个帧中执行任务。在Unity中,携程是一个可以暂停并在后续帧中从暂停处继续执行的方法。 二.携程写法 下面示例使用携程和Update打印前5帧的时间间隔,展示了携程的基础写法 using System.Colle…

vmware + ubuntu + 初始配置(超级用户权限、vim安装、ssh登陆、共享文件夹、git)

1 VMware Ubuntu下载与安装 下载与安装 2 使用超级用户权限 (1)执行命令:sudo passwd root 然后在弹出的密码中输入密码即可,具体如下: 第一个密码是当前用户密码 后面两个是root用户密码 //推荐使用一个密码 3 vi…

SEMIDRIVE X9E Flash 调试要点

一、前言 客户采用芯驰 X9E 平台做的 T-BOX 产品,因为客户选用的 Flash 型号不在 SemiDrive_Memory 支持列表里面,出现机器能烧录不能启动的问题。接下来我们对这个问题进行调试。 二、SEMIDRIVE X9E Flash 调试要点 ① 客户的板子 Flash 型号为 GD25LQ…

43集 ESP32 编译调试出错的解决方法汇总

43集 ESP32 编译调试出错的解决方法汇总 1、提示找不到如下头文件,分别对应adf的component #include “esp_peripherals.h” esp_peripherals #include “audio_element.h” audio_pipeline #include “audio_common.h” audio_pipeline 这几个头文件都是esp-adf里…

【全网首发】2024华为OD机试 E卷D卷抽中题库清单(全真题库,持续更新)含考点说明

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(E卷D卷A卷B卷C卷)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加…

Git 原理(提交对象)(结合图与案例)

Git 原理(提交对象) 这一块主要讲述下 Git 的原理。 在进行提交操作时,Git 会保存一个提交对象(commit object): 该提交对象会包含一个指向暂存内容快照的指针; 该提交对象还包含了作者的姓…

【MYSQL中数据库的约束以及表的设计】

MYSQL中数据库的约束和表的设计 一、数据库的约束1.1 NULL约束1.2 UNIQUE:唯一约束1.3 DEFAULT:默认值约束1.4 PRIMARY KEY:主键约束1.5 FOREIGN KEY :外键约束1.6 CHECK 约束 二、表的设计2.1 第一范式(1NF&#xff0…

Python面试宝典第49题:字符串压缩

题目 给你一个字符数组chars ,请使用下述算法进行压缩。 1、从一个空字符串s开始,对于chars中的每组连续重复字符 : (1)如果这一组长度为1 ,则将字符追加到s中。 (2)否则&#xff0c…

易灵思FPGA开发(一)——软件安装

一、资料下载 VF-T20F256-深圳市奥唯思科技有限公司_FPGA图像开发_MIPI (szovs.com) 二、软件安装 安装USB下载器驱动 双击第一个.msi文件进行安装 奥唯思FPGA网盘汇总 (szovs.com) 下载Gtkwave软件

初始MYSQL数据库(5)—— 索引

找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程(ಥ_ಥ)-CSDN博客 所属专栏: MYSQL 目录 索引的概念 索引选择的数据结构 MySQL中的页的相关知识 索引的分类 主键索引 普通索引 唯一索引 非聚集索引 回表查询…

基于python+django+vue的宠物服务管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的宠…

云原生(Cloud Native)简介及相关技术

云原生(Cloud Native)简介及相关技术 什么是云原生? 云原生(Cloud Native)是一种设计和开发应用程序的方法,旨在充分利用云计算的弹性、可扩展性和分布式架构优势。通过采用微服务架构、容器化、持续集成…

【自动驾驶】决策规划算法(一)决策规划仿真平台搭建 | Matlab + Prescan + Carsim 联合仿真基本操作

写在前面: 🌟 欢迎光临 清流君 的博客小天地,这里是我分享技术与心得的温馨角落。📝 个人主页:清流君_CSDN博客,期待与您一同探索 移动机器人 领域的无限可能。 🔍 本文系 清流君 原创之作&…

Java | Leetcode Java题解之第413题等差数列划分

题目: 题解: class Solution {public int numberOfArithmeticSlices(int[] nums) {int n nums.length;if (n 1) {return 0;}int d nums[0] - nums[1], t 0;int ans 0;// 因为等差数列的长度至少为 3,所以可以从 i2 开始枚举for (int i …

硬件工程师笔试面试——集成电路

目录 17、集成电路 17.1 基础 集成电路实物图 17.1.1 概念 17.1.2 集成电路的发展历程 17.1.3 集成电路的分类 17.1.4 集成电路的制造工艺 17.1.5 集成电路的应用 17.2 相关问题 17.2.1 集成电路的制造工艺中,光刻技术是如何实现的? 17.2.2 在集成电路设计中,如何…

【Python百日进阶-Web开发-FastAPI】Day801 - FastAPI是什么

文章目录 一、官网二、FastAPI是什么三、FastAPI特性3.1 基于开放标准3.2 自动生成文档3.3 更主流的 Python3.4 编辑器支持3.5 简洁3.6 验证3.7 安全性及身份验证3.8 依赖注入3.9 无限制"插件"3.10 测试四、Starlette 特性五、Pydantic 特性六、Python 类型提示简介6…

DERP靶机详解

靶机下载地址 https://www.vulnhub.com/entry/derpnstink-1,221/ 靶机配置 默认是桥接模式,切换为NAT模式后重启靶机 主机发现 arp-scan -l 端口扫描 nmap -sV -A -T4 192.168.229.158 访问网页 http://192.168.229.158/ 目录扫描 python dirsearch.py -u htt…