力扣刷题-二叉树-路径总和

112 路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例: 给定如下二叉树,以及目标和 sum = 22,
image.png
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

思路

参考:https://www.programmercarl.com/0112.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.html#%E6%80%9D%E8%B7%AF
递归函数什么时候要有返回值,什么时候没有返回值,特别是有的时候递归函数返回类型为bool类型。
那么接下来我通过详细讲解如下两道题,来回答这个问题:
112.路径总和
113.路径总和ii
这道题我们要遍历从根节点到叶子节点的路径看看总和是不是目标和。

递归

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

  1. 确定递归函数的参数和返回类型

参数:需要二叉树的节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?
如图所示:
image.png
图中可以看出,遍历的路线,并不要遍历整棵树(只需要有符合题意得时候 立刻返回就行),所以递归函数需要返回值,可以用bool类型表示。
所以代码如下:

# 我的问题就是遍历到一条路径底部时候 如何转到另一条路径 —— > 回溯def travelsal(self, node, count): # 递归第一步 参数为当前节点 另外采用一个计数器 遍历路径时候减去当前遍历节点的值

在python中 函数头不明确指明返回值类型 但自己要知道 返回值什么 为什么需要返回值

  1. 确定终止条件

首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。(逆向思维)
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
递归终止条件代码如下:

if not node.left and not node.right and count == 0:return True # 到达叶子节点 且count为0 说明当前路径刚好符合目标
if not node.left and not node.right:return False # 到达叶子节点 但是count不为0 因为路径有很多条 到达路径时候必然是叶子节点
  1. 确定单层递归的逻辑

因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
代码如下:

if node.left:count -= node.left.valif self.travelsal(node.left, count): # 递归处理节点return Truecount += node.left.val # 回溯 
# 右
if node.right:count -= node.right.valif self.travelsal(node.right, count):return Truecount += node.right.val # 回溯

完整代码:

class TreeNode(object):def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution(object):def hasPathSum(self, root, targetSum):""":type root: TreeNode:type targetSum: int:rtype: bool"""if not root:return False# self.travelsal(root, targetSum)return self.travelsal(root, targetSum - root.val) # 减去根节点的 再遍历之后的# 我的问题就是遍历到一条路径底部时候 如何转到另一条路径 —— > 回溯def travelsal(self, node, count): # 递归第一步 参数为当前节点 另外采用一个计数器 遍历路径时候减去当前遍历节点的值if not node.left and not node.right and count == 0:return True # 到达叶子节点 且count为0 说明当前路径刚好符合目标if not node.left and not node.right:return False # 到达叶子节点 但是count不为0 因为路径有很多条 到达路径时候必然是叶子节点# 左# if node.left:#     count -= node.left.val#     self.travelsal(node.left, count)#     count += node.left.val # 回溯 # 上面错误if node.left:count -= node.left.valif self.travelsal(node.left, count): # 递归处理节点return Truecount += node.left.val # 回溯 # 右if node.right:count -= node.right.valif self.travelsal(node.right, count):return Truecount += node.right.val # 回溯return False # 注意最后还要返回False 因为没有符合以上True的情况

迭代法

如果使用栈模拟递归的话,那么如果做回溯呢?
此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。

# 法二 迭代法
class Solution(object):def hasPathSum(self, root, targetSum):if not root:return False # 树为空 直接返回False# 采用前序遍历 中左右 stack = [(root, root.val)] # 栈里面存储的是节点及节点值while stack:node, path_sum = stack.pop()# 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回trueif not node.left and not node.right and path_sum == targetSum:return True#  # 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来if node.left:stack.append((node.left, path_sum + node.left.val))# 右节点if node.right:stack.append((node.right, path_sum + node.right.val))return False

113 路径总和2

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。(本题与上一题得区别是要找到所有的路径)
说明: 叶子节点是指没有子节点的节点。
示例: 给定如下二叉树,以及目标和 sum = 22,
image.png

思路

113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!
如图:
image.png
代码:

class TreeNode(object):def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right# 本题与112题不同的是 本题要找出所有路径 所以除了count计数之外 还需要一个列表来存储路径结果
class Solution(object):def __init__(self):self.result = [] # 总的结果self.path = [] # 单条路径结果def pathSum(self, root, targetSum):""":type root: TreeNode:type targetSum: int:rtype: List[List[int]]"""# 先清空 result 和 path# self.result.clear()# self.path.clear()del self.result[:] # 注意若是 del self.result则是删除整个对象 而加了[:]是删除其中的元素del self.path[:]if not root:return self.resultself.path.append(root.val) # 把根节点放入pathself.travelsal(root, targetSum - root.val)return self.resultdef travelsal(self, node, count): # 用node表示更一般化 容易理解if not node.left and not node.right and count == 0:self.result.append(self.path[:]) # 收获结果 符合题目的单条路径加到总结果中 注意是 self.path[:]return if not node.left and not node.right:returnif node.left: # 左节点不为空 空节点不遍历self.path.append(node.left.val)count -= node.left.valself.travelsal(node.left, count) # 递归节点count += node.left.val # 回溯self.path.pop() # 回溯 pop不需要传入参数if node.right: # 右节点不为空 空节点不遍历self.path.append(node.right.val)count -= node.right.valself.travelsal(node.right, count) # 递归节点count += node.right.val # 回溯self.path.pop() # 回溯return # 不需要返回值 直接一个return就行

self.result.append(self.path) 与 self.result.append(self.path[:]) 的区别:

  1. result.append(self.path) 将 self.path 添加到 result 列表中,但实际上它是对 self.path 的引用。这意味着如果后续更改了 self.path 的值,result 列表中的相应元素也会受到影响,因为它们引用相同的对象。这是因为 self.path 是一个可变对象(例如,列表、字典),并且在列表中仅存储了对该对象的引用。
self.path = [1, 2, 3]
result = []
result.append(self.path)self.path.append(4)
print(result)  # 输出 [1, 2, 3, 4]
  1. result.append(self.path[:]) 创造了 self.path 的一个副本,并将该副本添加到 result 列表中。这意味着即使后续更改了 self.path 的值,result 列表中的元素仍然保持不变,因为它们引用的是一个独立的副本。
self.path = [1, 2, 3]
result = []
result.append(self.path[:])self.path.append(4)
print(result)  # 输出 [1, 2, 3]

总结

本篇通过leetcode上112. 路径总和 和 113. 路径总和ii 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。
这两道题目是掌握这一知识点非常好的题目,大家看完本篇文章再去做题,就会感受到搜索整棵树和搜索某一路径的差别。
对于112. 路径总和,我依然给出了递归法和迭代法,这种题目其实用迭代法会复杂一些,能掌握递归方式就够了

参考:https://www.programmercarl.com/0112.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.html

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

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

相关文章

记录 | Visual Studio报错:const char*类型的值不能用于初始化char*类型

Visual Studio 报错: const char *”类型的值不能用于初始化“char *”类型的实体错误 解决办法: 1,强制类型转换,例如: char * Singer::pv[] {(char*)"other", (char*)"alto", (char*)"c…

arp协议

arp协议 ARP协议简介 在探讨计算机网络的复杂世界时,我们不可避免地会遇到地址解析协议(ARP)。ARP协议扮演着一个关键角色,它允许网络设备在发送数据时确定目标设备的物理地址,即媒体访问控制(MAC&#x…

Linux常见面试题30题详细答案解析(三)

1. 如何使用Linux中的系统日志进行故障排查和问题诊断? Linux中的系统日志记录了系统运行过程中的各种事件和错误信息。通过查看和分析系统日志,可以帮助管理员进行故障排查和问题诊断。了解如何查看和使用系统日志,可以提高故障排查和问题解…

1848_emacs_org-mode代码块环境

Grey 全部学习内容汇总: https://github.com/greyzhang/g_org 1848_emacs_org-mode代码块环境 这一部分主要是涉及到一些代码的执行、引用以及输出处理等功能。从之前我看的资料来说,更加偏重于可重现研究但不一定是文学式编程的必要部分。 内容来源…

git 上传大文件操作 lfs 的使用

我们要先去下载 下载后安装 我最后还是下载到了D:\git\Git\bin这个目录下 如何检查是否下载成功呢,用 git lfs install 在命令行运行就可以查看 下面怎么上传文件呢 首先我们还是要初始化文件的 git init 下一步输入命令 git lfs install 下一步 git lfs tra…

【小程序】-【

swiper、swiper-item轮播图 swiper是滑块视图容器。其中只可放置swiper-item组件。部分常用属性如下&#xff0c;其余属性详见&#xff1a;官方文档 <view class"banner"><swiperprevious-margin"30rpx"circularautoplayinterval"3000&q…

EasyExcel实现⭐️本地excel数据解析并保存到数据库的脚本编写,附案例实现

目录 前言 一、 EasyExcel 简介 二、实战分析 1.Controller控制层 2. service方法和方法实现 3.EasyExcel相关类 3.1 excel表实体类 3.2 自定义监听器类 4.测试 4.1 准备工作 4.2 断点调试 5.生成脚本文件 三、分析总结 章末 小伙伴们大家好&#xff0c;最近开发的时…

Ansible-playbook编译.yml脚本

1、playbook是什么&#xff1f; 在Ansible中&#xff0c;Playbook是用于配置、部署和管理被控节点的剧本。它由一个或多个play&#xff08;角色&#xff09;组成&#xff0c;每个play可以包含多个task&#xff08;台词&#xff0c;动作&#xff09;。使用Ansible的Playbook&am…

网络编程-认识套接字socket

文章目录 套接字概念端口号网络字节序 套接字类型流套接字数据报套接字 socket常见APIsocket函数bind函数listen函数accept函数connect函数sockaddr结构 套接字概念 socket套接字是进程之间一种通信机制&#xff0c;通过套接字可以在不同进程之间进行数据交流。在TCP/UDP中&…

如何开发一个prompt?prompt的使用有哪些原则?

提示词使用原则 如何开发一个跟自己预期结果接近的提示词&#xff1f;有哪些基本原则&#xff1f; 提示词迭代开发 写提示词时&#xff0c;第一次尝试是值得的&#xff0c;反复完善提示&#xff0c;获得越来越接近你想要的结果 原文来源于B站吴恩达提示工程教学公开课。…

低代码是美食!!!

一、什么是低代码 低代码是一种软件开发方法&#xff0c;通过图形化界面和少量手写代码&#xff0c;让开发者能够更迅速、简单地构建应用程序。相比传统的编码方式&#xff0c;低代码平台提供了可视化的开发工具和预构建的组件&#xff0c;使开发过程更加快捷高效。 二、低代码…

web前端之复制图片到div中、使用contenteditable属性把元素变为可编辑状态、FileReader

MENU 方式一方式二contenteditableFileReader 方式一 <div id"idEditor" class"w_100_ h_200" contenteditable></div>1、存在兼容性问题&#xff0c;而且contenteditable属性只是把div变为可编辑形式而已&#xff1b; 2、只能处理截屏形式的图…

ActionCLIP:A New Paradigm for Video Action Recognition

文章目录 ActionCLIP: A New Paradigm for Video Action Recognition动机创新点相关工作方法多模态框架新范式预训练提示微调 实验实验细节消融实验关键代码 总结相关参考 ActionCLIP: A New Paradigm for Video Action Recognition 论文&#xff1a;https://arxiv.org/abs/21…

7-5 时间类(Java for PTA)

设计一个名为Time 的类。这个类包含&#xff1a; 表示时间的数据域hour、minute 和second。一个以当前时间创建Time 对象的无参构造方法&#xff08;数据域的值表示当前时间&#xff09;。一个构造Time 对象的构造方法&#xff0c;这个对象有一个特定的时间值&#xff0c;这个…

服务端主动给客户端发消息?实战教学:使用Nestjs实现服务端推送SSE

前言 服务端消息推送SSE是常用的服务器消息通信手段&#xff0c;适用于服务器主动给客户端发送消息的场景&#xff0c;例如私信通知&#xff0c;扫描登录等都可以使用SSE实现。SSE的底层原理是客户端与服务端建立 HTTP 长链接。 Nestjs 框架内置了对SSE的支持&#xff0c;本文…

【算法】【动规】环绕字符串中唯一的子字符串

跳转汇总链接 &#x1f449;&#x1f517;动态规划算法汇总链接 1.5 环绕字符串中的子字符串 &#x1f517;题目链接 定义字符串 base 为一个 “abcdefghijklmnopqrstuvwxyz” 无限环绕的字符串&#xff0c;所以 base 看起来是这样的&#xff1a; "...zabcdefghijklmnop…

ES6 面试题 | 15.精选 ES6 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

C++:函数重载

1.函数重载概念 函数重载就是用同一个函数名定义的不同函数&#xff0c;当函数名和不同的参数搭配时函数的功能和含义不同。 2.实现函数重载的条件 同一个作用域&#xff0c;参数个数不同或者参数类型不同或者参数顺序不同(满足一个即可) void func(){} void func(int x){} v…

2024美赛全方位备赛教学/翻译/写作模版/翻译/软件/资料

本文字数20000&#xff0c;文章较长&#xff0c;建议先看目录&#xff0c;点击目录条目可以快速跳转。2024美赛大学生数学建模竞赛即将开始&#xff0c;不知道大家是否已经准备好相关资料如写作模板、常见算法的编程代码等等&#xff1f;评论区处有这些资料的下载方式。 文章结…

计算机网络:数据链路层(广域网、PPP协议、HDLC协议)

今天又学会了一个知识&#xff0c;加油&#xff01; 目录 一、广域网 二、PPP协议 1、PPP协议应满足的要求 2、PPP协议无需满足的要求 3、PPP协议的三个组成部分 4、PPP协议的状态图 5、PPP协议的帧格式 三、HDLC协议 1、HDLC的站&#xff08;主站、从站、复合站&…