算法打卡day16

今日任务:

1)513.找树左下角的值

2)112.路径总和

3)113.路径总和Ⅱ

4)106.从中序与后序遍历序列构造二叉树 

5)105.从前序与中序遍历序列构造二叉

513.找树左下角的值

题目链接:513. 找树左下角的值 - 力扣(LeetCode)

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。


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

示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

文章讲解:代码随想录 (programmercarl.com)

视频讲解:怎么找二叉树的左下角? 递归中又带回溯了,怎么办?| LeetCode:513.找二叉树左下角的值哔哩哔哩bilibili

层次遍历——思路:

首先最简单直接的就是采用层次遍历,收集每一层元素,最后返回最后一层的第一个元素result[-1][0]

稍微改进一下就是,遍历每一层时,只取队列的首位

# Definition for a binary tree node.
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right# 层次遍历
class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:if not root:return 0queue = collections.deque([root])result = []while queue:queue_size = len(queue)level = []for _ in range(queue_size):node = queue.popleft()level.append(node.val)if node.left:queue.append(node.left)if node.right:queue.append(node.right)result.append(level)return result[-1][0]# 改进def findBottomLeftValue2(self, root: Optional[TreeNode]) -> int:if not root:return 0queue = collections.deque([root])result = 0while queue:queue_size = len(queue)for i in range(queue_size):node = queue.popleft()if i == 0:result = node.valif node.left:queue.append(node.left)if node.right:queue.append(node.right)return result

递归(DFS)——思路

核心是找到最后一行的最左侧节点,这个节点不一定是左节点。如果采用递归的话

1.我们要明确递归函数的参数和返回值

这里我们需要记录深度,所以传参除了常规的节点外,还需要传入深度,遍历过程中还需注意深度的回溯。整个过程中,我们需要有一个变量记录最大深度,一个变量记录最大深度第一次出现时叶子节点的值,这两个参数可以作为传参,也可以设为全局变量。

返回值则为最大深度第一次出现时的叶子节点

2.确定终止条件

当遇到叶子节点时,需要更新最大深度,如果此时深度比之前记录的最大深度大,那么同时也要更新记录返回值的变量

3.确定单层递归的逻辑

保证先左后右即可,这样第一次出现最大深度的节点一定在最左侧

class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:self.result = Noneself.maxDepth = float('-inf')self.getLeftNode(root, 0)return self.resultdef getLeftNode(self, node, depth):# 判断该节点是否为叶子节点,是则终止if node.left is None and node.right is None:if depth > self.maxDepth:self.result = node.valself.maxDepth = depthreturnif node.left:depth += 1self.getLeftNode(node.left, depth)depth -= 1if node.right:depth += 1self.getLeftNode(node.right, depth)depth -= 1

精简版:隐藏回溯

# 递归精简(隐藏回溯)
class Solution3:def findBottomLeftValue(self, root: TreeNode) -> int:self.max_depth = float('-inf')self.result = Noneself.traversal(root, 0)return self.resultdef traversal(self, node, depth):if not node.left and not node.right:if depth > self.max_depth:self.max_depth = depthself.result = node.valreturnif node.left:self.traversal(node.left, depth + 1)if node.right:self.traversal(node.right, depth + 1)

112. 路径总和

题目链接:

112. 路径总和 - 力扣(LeetCode)

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

文章讲解:代码随想录 (programmercarl.com)

视频讲解:拿不准的遍历顺序,搞不清的回溯过程,我太难了! | LeetCode:112. 路径总和哔哩哔哩bilibili

112.路径总和——思路:

DFS深度优先遍历算法,前序遍历,每次遍历采用目标值去减当前节点,
一旦出现差值div为0,可以判断是否是叶子节点,是叶子节点返回true
如果遍历到叶子节点,div != 0 继续遍历
这题也要注意回溯过程

class Solution:def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:if not root:return Falsediv = targetSum - root.valreturn self.traversal(root, div)def traversal(self, node, div):# 终止条件if not node.left and not node.right:if div == 0:return Trueelse:return Falseif node.left:div -= node.left.valif self.traversal(node.left, div):  # 左return Truediv += node.left.val  # 回溯if node.right:div -= node.right.valif self.traversal(node.right, div):  # 右return Truediv += node.right.val  # 回溯return False

精简写法,隐藏回溯

class Solution2:def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:if not root:return Falseif not root.left and not root.right:if targetSum == root.val:return Trueelse:return Falsereturn self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)

113.路径总和 II 

题目链接:113. 路径总和 II - 力扣(LeetCode)

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树[5,4,8,11,None,13,4,7,2,None,None,5,1],以及目标和 sum = 22,
返回:[
    [5,4,11,2],
    [5,8,4,5]
   ]

 思路

DFS深度优先遍历算法,前序遍历,每次遍历采用目标值去减当前节点,
一旦出现差值div为0,可以判断是否是叶子节点,是叶子节点则保存当前节点
如果遍历到叶子节点,div != 0 继续遍历
这题也要注意回溯过程

class Solution:def PathSum(self, root: TreeNode, targetSum: int) -> list[list[int]]:self.result = []if not root:return []div = targetSum - root.valpath = [root.val]self.traversal(root, path, div)return self.resultdef traversal(self, node, path, div):# 终止条件if not node.left and not node.right :if div == 0:self.result.append(path.copy())returnif node.left:div -= node.left.valpath.append(node.left.val)self.traversal(node.left, path, div) # 左div += node.left.val  # 回溯path.pop()if node.right:div -= node.right.valpath.append(node.right.val)self.traversal(node.right, path, div) # 右div += node.right.val  # 回溯path.pop()

上面的代码中,有一个地方,要格外注意:当判断为叶子节点,且div为0时,我需要添加当前路径到列表中。

这里产生了一个问题,问题出现在将 path 添加到 self.result 中时,实际上添加的是 path 的引用而不是 path 的副本。这意味着当 path 发生变化时,已经添加到 self.result 中的路径也会随之变化。

解决这个问题的方法是,将 path 添加到 self.result 中时,添加 path 的副本而不是直接添加 path。这样就不会受到 path 变化的影响。我后来使用 path.copy() 来创建 path 的副本。也可以用path[:]

上面的代码好理解,但写的并不好,紧接进行改进  

1.避免全局变量:目前代码中使用了 self.result 作为全局变量来存储结果。虽然这种方法可以工作,但不推荐使用全局变量,因为它会增加代码的复杂性和维护成本。相反,可以将结果作为函数的返回值,这样可以使代码更加清晰和可维护。
2.递归中改成隐式回溯:在递归函数中不再修改传递的参数,而是在函数内部创建局部变量来处理路径和目标值。这样可以减少代码的复杂性,并降低出错的风险。
3.使用列表推导式简化代码:在遍历左右子树时,可以使用列表推导式来简化代码。这样可以使代码更加简洁和易读。

class Solution2:def PathSum(self, root: TreeNode, targetSum: int) -> list[list[int]]:result = []if not root:return resultself.traversal(root, [root.val], targetSum - root.val, result)return resultdef traversal(self, node, path: list[int], target, result):# 终止条件if not node.left and not node.right and target == 0:result.append(path.copy())return# 左子树if node.left:self.traversal(node.left, path+[node.left.val], target - node.left.val, result)# 右子树if node.right:self.traversal(node.right, path+[node.right.val], target - node.right.val, result)

在上面代码中,我企图将路径path与差值div参数均隐藏回溯

div隐藏回溯没有什么问题,路径隐藏回溯时,我就很常规的将path.append(node.left.val)作为参数参入,存在一个潜在的问题。

在Python中,list.append() 方法会直接修改原列表,并且返回值为 None,因此我在递归调用时实际上传递的是 None,而不是修改后的 path。这可能导致程序运行时出现错误。

后来我使用 path + [node.left.val] 的方式创建新的路径,在递归调用时传递这个新的路径,而不是直接修改原路径 path。

106.从中序与后序遍历序列构造二叉树

题目链接:

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

根据一棵树的中序遍历与后序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
例如,给出中序遍历 inorder = [9,3,15,20,7]  后序遍历 postorder = [9,15,7,20,3]
返回二叉树[3,9,20,None,None,15,7]

文章讲解:代码随想录 (programmercarl.com)

视频讲解:坑很多!来看看你掉过几次坑 | LeetCode:106.从中序与后序遍历序列构造二叉树哔哩哔哩bilibili

思路:

中:左中右  后:左右中

核心:后序遍历列表中,最后一个肯定是中节点(也是根节点),通过这个中节点去拆分中序遍历列表的左右区间

1.后序数组为0,空节
2.后序数组最后一个元素为节点元素
3.寻找中序数组位置作切割点
4.切中序数组
5.切后序数组
6.递归处理中后序中的左右区间
7.返回答案

class Solution:def buildTree(self, inorder: list[int], postorder: list[int]) -> Optional[TreeNode]:# todo 1.后序数组为0,空节点if not postorder:return# todo 2.后序数组最后一个元素为节点元素node = TreeNode(postorder[-1])# todo 3.寻找中序数组位置作切割点index = inorder.index(node.val)# todo 4.切中序数组left_inorder = inorder[:index]right_inorder = inorder[index+1:]# todo 5.切后序数组size = len(left_inorder)left_postorder = postorder[:size]right_postorder = postorder[size:len(postorder)-1]# todo 6.递归处理中后序中的左右区间node.left = self.buildTree(left_inorder,left_postorder)node.right = self.buildTree(right_inorder,right_postorder)# todo 7.返回答案return node

105.从前序与中序遍历序列构造二叉树

题目链接:

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

思路:

中:左中右  前:中左右 
1.前序数组为0,空节点
2.前序数组第一个元素为节点元素
3.寻找中序数组位置作切割点
4.切中序数组
5.切前序数组
6.递归处理前中序中的左右区间
7.返回答案

class Solution:def buildTree(self, preorder: list[int], inorder: list[int]) -> Optional[TreeNode]:# todo 1.前序数组为0,空节点if not preorder:return# todo 2.前序数组第一个元素为节点元素node = TreeNode(preorder[0])# todo 3.寻找中序数组位置作切割点index = inorder.index(preorder[0])# todo 4.切中序数组left_inorder = inorder[:index]right_inorder = inorder[index+1:]# todo 5.切前序数组size = len(left_inorder)left_preorder = preorder[1:size+1]right_preorder = preorder[size+1:]# todo 6.递归处理前中序中的左右区间node.left = self.buildTree(left_preorder,left_inorder)node.right = self.buildTree(right_preorder,right_inorder)# todo 7.返回答案return node

感想:

这两题不好想,首先要知道前中后序遍历的顺序

前序:中左右     中序:左中右     后序:左右中

前中序与后中序都能确定唯一的二叉树,但是前后序不行

举个例子

tree1 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。

tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。

那么tree1 和 tree2 的前序和后序完全相同,这是一棵树么,很明显是两棵树!

所以前序和后序不能唯一确定一棵二叉树

为什么非得需要中序呢

这也是解这题的核心。我们可以由前后序列表知道中间节点的位置,正是因为中序遍历是左中右,所以我们继而用这个中节点去拆分中序列表中左右子树区间,从而能进行递归。

如果我们拿到的是前后序,前序为中左右,后序为左右中国,及时知道中间节点也无法去拆分左右子树区间。所以我们能由前中序遍历或中后序遍历确定唯一的二叉树

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

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

相关文章

js【详解】深拷贝

什么是深拷贝? 对于引用类型的数据,才有深浅拷贝的说法 浅拷贝 :执行拷贝的变量只复制被拷贝变量内存的引用数据的地址。 被拷贝变量内地址指向的数据发生变化时,执行拷贝的变量也会同步改变 深拷贝: 在堆内存中开…

为什么静态成员函数不能是虚函数

在面向对象编程中,静态成员函数和虚函数都是常见的概念,但它们之间存在着本质上的差异。由于其特性上的差异,静态成员函数不能声明为虚函数。下面我们来探讨一下为什么静态成员函数不能是虚函数。 我在网上查到最多的说法是静态函数没有this指…

Spring Cloud Alibaba 整合Seata分布式事务

目录 前言步骤引入相关maven依赖添加相关配置Client端配置注册中心Server端配置注册中心Seata-Server相关配置启动seata-server 使用方法Seata AT 模式整体机制 步骤初始化表结构标记注解GlobalTransactional 总结 前言 在数字化转型的浪潮下,企业业务系统的复杂度…

【spring】@Component注解学习

Component介绍 Component 是 Spring 框架中的一个注解,用于将一个类标记为 Spring 上下文中的一个组件。当一个类被标记为 Component 时,Spring 容器会在启动时自动扫描并实例化这个类,并将其注册到 Spring 上下文中。 Component 注解可以用…

集合深入------理解底层。

集合的使用 前提:栈、堆、二叉树、hashcode、toString()、quesalus()的知识深入和底层理解。 1、什么是集合 集合就是咋们所说的容器 ​ 前面我们学习过数组 数组也是容器 ​ 容器:装东西的 生活中有多少的容器呀? 水杯 教室 酒瓶 水库 只要是…

Mysql的存储引擎

目录 1.存储引擎概念 2.常用的存储引擎 2.1MyISAM 2.1.1MyISAM的特点 2.1.2MyISAM表支持3种不同的存储格式: 2.1.3MyISAM适用的生产场景举例 2.2InnoDB 2.2.1InnoDB特点介绍 2.2.2InnoDB适用生产场景分析 2.2.3InnoDB与MyISAM 区别 3.企业选择存储引擎依据…

C++ 扫描当前路径下文件并删除大文件

C 扫描当前路径下文件并删除大文件 C获取当前路径扫描文件路径下规定后缀名称的文件计算文件大小 1. 获取当前路径 使用<Windows.h>中的GetCurrentDirectory方法实现&#xff0c;单独编写验证程序如下&#xff1a; #include<iostream> #include<Windows.h&g…

Kotlin零基础入门到进阶实战

教程介绍 Kotlin现在是Google官方认定Android一级开发语言&#xff0c;与Java100%互通&#xff0c;并具备诸多Java尚不支持的新特性&#xff0c;每个Android程序员必备的Kotlin课程&#xff0c;每个Java程序员都需要了解的Kotlin&#xff0c;掌握kotlin可以开发Web前端、Web后…

pytorch中tensor类型转换的几个函数

目录 IntTensor转FloatTensor FloatTensor转IntTensor Tensor类型变为python的常规类型 IntTensor转FloatTensor .float函数&#xff1a; FloatTensor转IntTensor .int函数 Tensor类型变为python的常规类型 item函数

java项目将静态资源中的文件转为浏览器可访问的http地址

新增一个类叫啥无所谓&#xff0c;主要是实现 WebMvcConfigurer 加上注解 Configuration项目启动时加入bean中 只操作addResourceHandlers这一个方法 其他都没用 文章下方附带一个简易的上传图片代码 package cn.exam.config;import org.springframework.context.annotati…

STM32学习笔记(2)- 软件keil5安装和新建工程

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 软件安装…

​ 翻译 《The Old New Thing》

今天开始&#xff0c;为大家翻译微软优秀的技术专栏 The Old New Thing。 由微软高级工程师 Raymond Chen 撰写。该专栏起初是一个博客&#xff0c;后来也出版了同名书籍。专栏主要围绕 Windows 操作系统的开发和设计展开&#xff0c;涵盖了 Windows 平台的历史、技术细节、编程…

JMeter CSV 参数文件的使用方法

.在 JMeter 测试中&#xff0c;参数化是非常重要的&#xff0c;参数化允许我们模拟真实世界中的各种情况。本文我们将探讨如何在 JMeter 中使用 CSV 参数文件。 创建 CSV 文件 首先&#xff0c;我们需要创建一个逗号分隔的值&#xff08;CSV&#xff09;文件&#xff0c;其中…

http和socks5代理哪个好?

HTTP代理和SOCKS5代理各有其优缺点&#xff0c;但就隐蔽性而言&#xff0c;SOCKS5代理通常比HTTP代理更隐蔽。以下是它们的比较&#xff1a; HTTP代理&#xff1a; 透明性较高&#xff1a;HTTP代理在HTTP头中会透露原始客户端的IP地址&#xff0c;这使得它相对不太隐蔽。…

uni-app纵向步骤条

分享一下项目中自封装的步骤条&#xff0c;存个档~ 1. 话不多说&#xff0c;先看效果 2. 话还不多说&#xff0c;上代码 <template><!-- 获取一个数组&#xff0c;结构为[{nodeName:"流程发起"isAudit:falsetime:"2024-02-04 14:27:35"otherDat…

【Flink】Flink 处理函数之基本处理函数(一)

1. 处理函数介绍 流处理API&#xff0c;无论是基本的转换、聚合、还是复杂的窗口操作&#xff0c;都是基于DataStream进行转换的&#xff0c;所以统称为DataStreamAPI&#xff0c;这是Flink编程的核心。 但其实Flink为了更强大的表现力和易用性&#xff0c;Flink本身提供了多…

Qt程序可执行文件打包

目录 一、新建一个目录二、命令行2.1 添加临时变量2.2 打包命令 三、添加动态库四、普通 Qt 项目打包 Qml 项目打包 笔者写的python程序打包地址&#xff08;https://blog.csdn.net/qq_43700779/article/details/136994813&#xff09; 一、新建一个目录 新目录(例如test)用以…

Vue.js前端开发零基础教学(三)

目录 2.6 计算属性 2.7侦听器 2.8 样式绑定 2.8.1 绑定class属性 2.8.2 绑定style属性 2.9 阶段案例——学习计划表 2.6 计算属性 概念&#xff1a;Vue提供了计算属性来描述依赖响应式数据的复杂逻辑。 计算属性可以实时监听数据的变化&#xff0c;返回一个计算…

真假“长文本”,国产大模型混战

文&#xff5c;郝 鑫 Kimi有多火爆&#xff1f;凭一己之力搅乱A股和大模型圈。 Kimi概念股连日引爆资本市场&#xff0c;多个概念股随之涨停。在一片看好的态势中&#xff0c;谁都想来沾个边&#xff0c;据光锥智能不完全统计&#xff0c;目前&#xff0c;至少有包括读客…

【蓝桥杯知识点】浮点数二分(开n次方根再也不会超时啦!)

今天继续学习基础算法&#xff01;这篇文章介绍了二分的另一种应用——浮点数二分&#xff0c;可以用于开n次方根的计算&#xff0c;会使时间大大缩短&#xff01;我偷偷问过电脑编译器了&#xff0c;它说它喜欢优化过的算法哈哈哈哈~相信你也会喜欢的&#xff01; PS&#xff…