【二叉树】【2.1遍历二叉树】【刷题笔记】【灵神题单】

news/2024/11/27 15:44:15/

关注二叉树的三个问题:

  1. 什么情况适合自顶向下?什么时候适合用自底向上
  2. 一般来说,DFS的递归边界是空节点,什么情况下要额外把叶子节点作为递归边界?
  3. 在什么情况下,DFS需要有返回值?什么情况不需要有返回值?

三种遍历顺序

本质就是根节点位置决定前中后序遍历

  1. 前序遍历
    • 顺序根节点 - 左子树 - 右子树。
    • 特点:先处理根节点,然后依次深入左子树和右子树进行相同规则的遍历。可以快速获取根节点相关信息,适用于复制二叉树等操作。
  2. 中序遍历
    • 顺序:左子树 - 根节点 - 右子树。
    • 特点:对于二叉搜索树,能得到有序的节点序列。先深入左子树,再访问当前层根节点,最后遍历右子树,常用于二叉搜索树的排序相关操作。
  3. 后序遍历
    • 顺序:左子树 - 右子树 - 根节点
    • 特点:最后处理根节点,在删除二叉树节点或者需要先获取左右子树信息(如计算二叉树高度)的场景比较适用,先递归处理左右子树,最后处理根节点。

144.二叉树的前序遍历

# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:res = []def dfs(node):if node is None: returnres.append(node.val)    # 根节点值 dfs(node.left)		    # 左节点dfs(node.right)			# 右节点dfs(root)return res

94.二叉树的中序遍历

# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:res = []def dfs(node):if node is None: returndfs(node.left)res.append(node.val)dfs(node.right)dfs(root)return res

145.二叉树的后序遍历

# 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:res = []def dfs(node):if not node: return dfs(node.left)dfs(node.right)res.append(node.val)dfs(root)return res

872.叶子相似的树

# 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 leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool:# dfs 递归到叶子节点时(当前节点无左右子节点),将当前节点值放入res=[]# 用啥顺序遍历?def dfs(node):ans = []# 当前空节点,直接returnif not node:return# 递归到了叶子节点if node.left is node.right:ans.append(node.val)# 前序遍历?dfs(node.left)dfs(node.right)return ansreturn dfs(root1) == dfs(root2)
——————————————————————
解答错误
21 / 47 个通过的测试用例官方题解
输入
root1 =
[1,2,3]
root2 =
[1,3,2]添加到测试用例
输出
true
预期结果
false

以为是递归顺序的原因,换成了中序遍历,结果并没有改变。

这段代码的核心错误在于每次每次调用dfs函数,都重新初始化了一个空列表[ ],这样就无法正确地在整棵树地遍历过程中收集到所有叶子节点的值到一个统一的列表!

正确做法是,在函数外部初始化结果列表ans1和ans2,然后将这个列表不断递归更新,每次都传入下一层递归函数!
class Solution:def leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool:# dfs 递归到叶子节点时(当前节点无左右子节点),将当前节点值放入res=[]# 用啥顺序遍历?ans1 =[]ans2 = []def dfs(node, ans):# 当前空节点,直接returnif not node:return           # 递归到了叶子节点if node.left is None and node.right is None:ans.append(node.val)                                       # 前序遍历?dfs(node.left, ans)dfs(node.right, ans)return ansans1 = dfs(root1, ans1)ans2 = dfs(root2, ans2)return ans1 == ans2

LCP44.开幕式焰火

DFS + 前序遍历 + set()

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:def numColor(self, root: TreeNode) -> int:# DFS + 前序遍历 + set()s = set()def dfs(node, s):if node is None:returns.add(node.val)dfs(node.left, s)dfs(node.right, s)dfs(root, s)return len(s)

或者把s非重复字典,放到dfs函数外面,也可以实现这个逻辑。为什么?

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:def numColor(self, root: TreeNode) -> int:# DFS + 前序遍历 + set()s = set()def dfs(node):if node is None:returns.add(node.val)dfs(node.left)dfs(node.right)dfs(root)return len(s)

两种方式区别在于 set 的定义位置与传递形式不同,效果一样是因为都利用 set 去重特性,遍历二叉树时添加节点值并最终返回其元素个数来统计不同节点值数量。

BFS + 层遍历 + 队列

层次遍历顺序:按照树的层次一层一层地向下遍历,直到队列为空。
这里地遍历顺序是广度优先地按层遍历,从根节点开始,每层从左到右一次访问节点。

# 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 numColor(self, root):s = set()self.search(s, root)return len(s)def search(self, s, node):if node is None:returnq = []q.append(node)while q:current_node = q.pop(0)s.add(current_node.val)if current_node.left:q.append(current_node.left)if current_node.right:q.append(current_node.right)

404.左叶子之和

  • 返回所有左叶子值之和
  • 为了理解如何找到左叶子,先不完成这道题的要求,先写一个dfs来判断如何找到左叶子。

DFS写法

def (node, is_left):if node is None:return# 判断是否为左叶子节点if node.left is None and node.right is None and is_left:res.append(node.val)dfs(node.left, True)dfs(node.right, False)dfs(root, False)

BFS写法

class TreeNode:def __inti__(self, val=0, left=None, right=None):self.val = valself.right = rightself.left = leftclass Solution:def sumOfLeftLeaves(self, root):res = []if root is None:return resultq = []q.append((root, False))while q:current_node, is_left = q.pop(0)# 判断是否是左叶子节点if current_node.left is None and current_node.right is None and s_left:result.append(current_node.val)if current_node.left:q.append((current_node.left, True))if current_node.right:q.append((current_node.right, False))return result 			

以上是如何遍历左叶子的方法
下面完成本题任务,加和所有左叶子值

# 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 sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:res = 0def dfs(node, is_left):nonlocal resif node is None:return# 关键逻辑,is_left递归决定是否dfsif node.left is None and node.right is None and is_left:res += node.valdfs(node.left, True)dfs(node.right, False)dfs(root, False)return res
一开始没加nonlocal一直通过不了!!
如果dfs函数内部没有用nonlocal声明res,python会认为在dfs内部重新定义了一个局部变量res,(尽管我实际上是想操作外层函数中的那个res),并且因为我在用res之前没有对这个新认为的局部变量进行初始化,所以会抛出unboundlocalerrro的错误,提示无法访问一个没有关联值的局部变量
当我加了nonlocal res后,就明确告诉了python解释器,在dfs函数中操作的res是来自外层函数的那个以及已经初始化过的局部变量,这样就能正确找到。

671.二叉树中第二小的节点

  • 这道题很难么?我认为只要检查根节点的左右子节点就行,如果根节点的左右子节点都相等,才需要递归检查左右子树,
  • 直到发现第一次不相等,不等于根节点那个值就是第二大的值,我这个思路对不对?

不对!!!

  1. 类比欧冠比赛来理解二叉树问题
    • 冠军(根节点最小值)的确定方式
      • 在欧冠比赛中,冠军是通过层层比赛筛选出来的最强队伍。在二叉树中,根节点的值被定义为左右子节点中的最小值(root.val = min(root.left.val, root.right.val)),这就好像冠军是从左右两个“分区”(左右子树)中选出的最小值一样。
    • 第二强队伍(第二小值)的复杂性
      • 在欧冠比赛中,亚军不一定是第二强的队伍。因为比赛的赛制是淘汰赛,可能有其他很强的队伍在和冠军队伍比赛前就被淘汰了,这些队伍也许比亚军更强。类比到二叉树中,只看根节点的左右子节点来确定第二小的值是不准确的。就像比赛中有很多队伍在淘汰赛过程中被淘汰一样,二叉树中的节点值也存在多种情况。
      • 例如,在二叉树中,即使根节点的左右子节点相等,在子树的更深处可能存在比根节点大,但比当前左右子节点小的值,这就好像在比赛中,有一些队伍虽然没有直接和冠军竞争,但实力介于冠军和我们最初认为的“亚军”(根节点左右子节点)之间。
    • 第二强队伍(第二小值)与冠军(根节点最小值)的关联
      • 你说的“第二强的一定和冠军交过手”类比到二叉树中,有一定的相似性。在二叉树中,真正的第二小的值一定是和根节点的值(最小的值)有比较关系的。它要么是根节点左右子节点中的较大值(如果存在差异),要么是在子树中比根节点值大的其他值。但不能简单地通过根节点左右子节点来判断,而是要遍历整个二叉树,就像要考虑整个欧冠比赛的所有队伍一样,来找到所有比根节点值大的节点值,然后从中确定第二小的值。
  2. 总结
    • 这个类比很形象地说明了不能简单地根据二叉树的局部结构(如根节点左右子节点)来确定第二小的值,而是要像考虑整个欧冠比赛的队伍情况一样,全面地考虑二叉树中所有节点的值,才能准确地找到第二小的值。

所以本质上这道题还是遍历

遍历 + set() + 排序
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = Noneclass Solution:def findSecondMinimumValue(self, root: TreeNode) -> int:"""先序遍历+set"""s = set()def dfs(node):if not node:return           nonlocal ss.add(node.val)dfs(node.left)dfs(node.right)dfs(root)min1 = float('inf') # 最小min2 = float('inf') # 第二小# 则关键逻辑是找到s中有没有一个val,满足 min1 < val < min2 # 如果for val in s:if val < min1:     # 存在一个val比最小的min1还要小,就说明要更新min1为val,且更新min2为valmin2 = min1    # 更新min2min1 = val     # elif val < min2 :  # val比min1大比min2小,就是第二大| 或者等于,也就是第二大的值!!!min2 = val     # min2已经更新为上一轮的min1了!,此时val就是第二大的min2!return -1 if min2 == float('inf') else min2       

另一种写法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = Noneclass Solution:def findSecondMinimumValue(self, root: TreeNode) -> int:"""先序遍历"""def helper(root, val): # 当前要遍历的节点,用于比较的值val,调用的时候直接调用根节点值,也就是最小值!!!            if not root:     # 递归终止条件,到头return -1    # 这道题找值,这里应该返回值,有的题不返回值# 关键逻辑:val是要比较的值,root.val是当前节点的值if root.val > val:# 当前节点值大于参考值,当前值有可能是!!第二小!!return root.val# 递归遍历左右子树,且传入当前节点值作为参考# 如果走到头都没有发现比val大的,返回-1表示这条路不存在left = helper(root.left, val)  # 注意这里val!并没有在每次递归时改变!!right = helper(root.right, val) # !val始终时根节点,也就是最小!!# 处理左右子树返回的结果!如果某个子树返回的值是-1,即到头没发现第二小!就返回另一边树的值if left < 0:return rightif right < 0:return left# 如果左右都有返回值,小的才是我们要找的第二小!return min(left, right)return helper(root, root.val)    
  1. 定义辅助函数
    • 定义 helper 函数,接受当前节点 root 和参考值 val(初始为根节点值)。
  2. 递归遍历与判断
    • 若当前节点为空,返回 -1。
    • 若当前节点值大于参考值,返回该节点值(可能是第二小值)。
    • 递归遍历左右子树,获取左右子树中可能的第二小值 leftright
  3. 处理子树返回结果
    • left 小于0,返回 right;若 right 小于0,返回 left;若都大于等于0,返回 min(left, right)
  4. 最终调用
    • 外部调用 helper 函数从根节点开始遍历二叉树,返回最终找到的第二小值(若不存在则为 -1)。

总的来说,这段代码通过深度优先搜索(DFS)的方式递归地遍历二叉树,在遍历过程中根据节点值与参考值的比较以及左右子树的返回结果来逐步确定可能的第二小的值,直到遍历完整个二叉树得出最终答案。


http://www.ppmy.cn/news/1550383.html

相关文章

C语言实例之9斐波那契数列实现

1. 斐波那契数列简介 斐波那契数列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又称黄金分割数列&#xff0c;因数学家莱昂纳多・斐波那契&#xff08;Leonardo Fibonacci&#xff09;以兔子繁殖为例子而引入&#xff0c;故又称为 “兔子数列”。 它的特点是从第三…

C++ 中的多继承

C 中的 多继承&#xff08;Multiple Inheritance&#xff09;是指一个类可以同时继承自多个父类。与单继承&#xff08;Single Inheritance&#xff09;不同&#xff0c;子类在多继承中可以从多个父类继承属性和方法。其基本语法如下&#xff1a; class ClassA {// ClassA 的成…

【前端学习笔记】AJAX、axios、fetch、跨域

1.介绍 AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;异步的JS和XML。通过 AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据。AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组合在一起使用的新方式。 X…

PHP实现插入排序

插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;适用于少量数据的排序。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。以下是一个用PHP实现插入排序…

代码随想录算法训练营第五十八天|Day58 图论

拓扑排序精讲 https://www.programmercarl.com/kamacoder/0117.%E8%BD%AF%E4%BB%B6%E6%9E%84%E5%BB%BA.html 拓扑排序的背景 本题是拓扑排序的经典题目。 一聊到 拓扑排序&#xff0c;一些录友可能会想这是排序&#xff0c;不会想到这是图论算法。 其实拓扑排序是经典的图论问…

基于nxp LS1046+fpga的嵌入式系统中虚拟化设备的设计与实现

3 虚拟化设备仿真平台设计 本文需要设计和实现的虚拟化设备需要搭建一个仿真平台&#xff0c;一个完善的仿真平台才 是一种虚拟化设备能搭建起来的关键&#xff0c;仿真平台的搭建需要一定条件的硬件环境&#xff0c;更为 主要的是软件环境&#xff0c;下文就要详细介绍此虚…

红外小目标检测

目录 背景概述算法原理演示效果核心逻辑 使用方式基础镜像配置环境直接运行 参考文献 文章声明&#xff0c;非广告&#xff0c;仅个人体验。 背景 红外图像在许多领域中都有所应用。例如军事领域中&#xff0c;经常需要通过红外成像设备对远距离的目标进行侦察和监视&#xff…

tableau-制作30个图表

制作条形图 步骤: 1、横轴是数值,对应了某一个度量值,纵轴是一个标签 战区的成交额,条形图横轴是战区,纵轴是成交额 下钻条形图 1、增加业务架构-战区右键点击,分层结构,增加分层结构 调整业务架构,将战区,城市,小组移动到业务架构下方 此时的条形图上方有➕号展开后…