513. 找树左下角的值
中等
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
读题便知用层序简单,所以这道题更适合用迭代。
❌向左遍历到最后一个(不一定是最底层的)
在树的最后一行找到最左边的值(不一定是左孩子)。
✅判断最后一行:深度为最大值的叶子结点
✅判断最左边:优先左的遍历都可以(本题不需要处理中节点,所以前中后遍历都可)
递归
递归三部曲
- 参数和返回值:root;两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。
- 终止条件:遇到叶子结点时,更新最大深度(⚠️何时返回result)
- 单层遍历逻辑:左/右孩子存在,则深度加一并遍历和回退
⚠️:traversal函数做的是当遇到叶子结点时,更新两个全局变量的值
回溯发生在递归函数返回时,即当一个递归调用结束后,控制权会返回到其调用者(上一个节点)。
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.max_len = -inf
self.result = root.val
self.traversal(root, 0) # 更新self.result 和 self.max_depth
return self.result
def traversal(self, root, depth):
if root.left is None and root.right is None:
if depth > self.max_len:
self.max_len = depth
self.result = root.val
return
# 找到叶子节点时需要更新 self.result并return
# 但不应该在 traversal 方法中直接返回 self.result,因为这样会导致在遍历过程中提早结束。
# 左
if root.left:
# depth+=1
# self.traversal(root.left,depth)
# depth-=1 # 回退
self.traversal(root.left,depth+1) # depth+1没有改变depth的值,隐藏回溯
# 右
if root.right:
self.traversal(root.right,depth+1)
# traversal 方法不需要返回任何值,因为它的主要作用是更新 self.result 和 self.max_depth。
- 递归求深度的写法:110 平衡二叉树
l_height = get_height(node.left) r_height = get_height(node.right) return max(l_height,r_height)+1 # 求高度
(其实在文章中110用的是中序遍历求高度),求深度应该用前序
if node.left: # 左孩子存在 self.getDepth(node.left, depth + 1) #遍历更新结果并回溯 if node.right: self.getDepth(node.right, depth + 1)
- 递归中其实隐藏了回溯,在257 所有二叉树的路径中讲解了究竟哪里使用了回溯,哪里隐藏了回溯。
- 层次遍历,在102 层序遍历深度讲解了二叉树层次遍历。
迭代(队列实现层序)
✅先把右儿子入队,再把左儿子入队,这样最后一个出队的节点就是左下角的节点了。
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
q = deque([root])
while q:
node = q.popleft()
if node.right: q.append(node.right)
if node.left: q.append(node.left)
return node.val
112. 路径总和
简单
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
递归三部曲:
1、参数和返回值:
root;本题我们要找一条符合条件的路径,所以递归函数需要返回值bool类型
2、确定终止条件:
递减,遇到叶子结点判断targetnum == 0
3、确定单层递归的逻辑
- 递归地检查左子树和右子树,看看是否在其中一条路径上存在满足条件的路径。
- 使用逻辑或 (
or
) 运算符,意味着只要任意一条路径满足条件,整个方法就会返回True
。
# 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
targetSum -= root.val
if root.left is None and root.right is None:
return targetSum == 0
return self.hasPathSum(root.left,targetSum) or self.hasPathSum(root.right,targetSum)
113. 路径总和 II
中等
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
不是很理解pop()的时机
# 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) -> List[List[int]]:
path = []
result = []
self.findpath(root, targetSum, path, result)
return result
def findpath(self, root, targetSum, path, result):
if root is None:
return []
path.append(root.val) # 遍历时加入当前节点值
targetSum -= root.val # 递减
if root.left is None and root.right is None and targetSum == 0:
result.append(path.copy()) # 遍历到叶子结点且符合路径总和,path加入result
self.findpath(root.left, targetSum, path, result) # 向左递归遍历
self.findpath(root.right, targetSum, path, result)
path.pop()
106. 从中序与后序遍历序列构造二叉树
中等
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
- 后序序列最后一个节点为根节点
- 切中序数组:在中序数组中找到根节点作为切割点,分出左、右子树
- 切后序数组
- 递归处理左区间、后区间
方法
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if not postorder: # 空节点
return None
left_size = inorder.index(postorder[-1]) # 左子树的大小
left = self.buildTree(inorder[:left_size], postorder[:left_size])
right = self.buildTree(inorder[left_size + 1:], postorder[left_size: -1])
return TreeNode(postorder[-1], left, right
存在的问题:
- list.index()方法的时间复杂度是 O(n),因为它需要遍历整个列表来查找元素.
- 如果
postorder[-1]
不在inorder
列表中,调用inorder.index()
会抛出ValueError
。需要先确认元素存在。 inorder[:left_size]
:这会创建一个新的列表,包含inorder
列表中从索引0
到left_size - 1
的元素。这是一个复制操作,因为它会生成一个新列表。
所以算法复杂度为(时间+空间)
优化
- 用一个哈希表(或者数组)预处理 inorder 每个元素的下标,这样就可以 O(1) 查到 postorder[n−1] 在 inorder 的位置,从而 O(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 buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
index = {x: i for i, x in enumerate(inorder)}
# x = inorder[i]
def dfs(in_l: int, in_r: int, post_l: int, post_r: int):
if post_l == post_r:
# 后序的左边界和右边界相同 没子树
return None
left_size = index[postorder[post_r - 1]] - in_l
left = dfs(in_l, in_l + left_size, post_l, post_l + left_size)
right = dfs(in_l + 1 + left_size, in_r, post_l + left_size, post_r - 1)
return TreeNode(postorder[post_r - 1], left, right)
return dfs(0, len(inorder), 0, len(postorder))
时间复杂度:O(n). 空间复杂度:O(n)
105. 从前序与中序遍历序列构造二叉树
中等
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
index = {x: i for i, x in enumerate(inorder)}
# 哈希表,将中序遍历的每个值与其索引对应起来
def dfs(pre_l: int, pre_r: int, in_l: int, in_r:int) -> Optional[TreeNode]:
if pre_l == pre_r:
return None
left_size = index[preorder[pre_l]] - in_l
# index[preorder[pre_l]]是根节点的值在字典中的索引-in_l也是索引
left = dfs(pre_l + 1, pre_l + 1 + left_size, in_l, in_l + left_size)
# 左闭右开 pre+1跳过根节点
right = dfs(pre_l + 1 + left_size, pre_r, in_l + 1 + left_size, in_r)
return TreeNode(preorder[pre_l], left, right)
"""
创建一个新节点,并将其左右子树链接在一起。递归地创建每个节点,并把它们连接到二叉树的正确位置
假设我们正在构建上面提到的例子,
当前递归层正是构建根节点 20
当调用 return TreeNode(preorder[2], left, right) 时
preorder[2] 返回 20。left 是 TreeNode(15)(左子树)。
right 是 TreeNode(7)(右子树)
"""
return dfs(0, len(preorder), 0, len(inorder))
⚠️ 前序和后序无法构造树