前言
513.找树左下角的值
重点体会size的标记作用。
113. 路径总和II
本节只讲递归法,实际上也可采用迭代法求解问题。“112.路径总和”也可以用类似方法求解。
首先是有错误的题解(连测试用例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 pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if root==None: return []
self.curPath, self.result = [root.val], []
self.count = targetSum - root.val
self.traverse(root)
return self.result
def traverse(self, node):
if not node.left and not node.right and self.count==0:
self.result.append(self.curPath)
if node.left:
self.curPath.append(node.left.val)
self.count -= node.left.val
self.traverse(node.left)
self.curPath.pop()
self.count += node.left.val
if node.right:
self.curPath.append(node.right.val)
self.count -= node.right.val
self.traverse(node.right)
self.curPath.pop()
self.count += node.right.val
return
是不是没有头绪?其实俺也一样,一开始想破了脑袋都没找到bug,于是灵机一动使用Deepseek帮我分析,DS用了34秒就把问题解决了,还顺带分析了代码时间、空间复杂度都是O(n),并且直接指出我的代码可以通过LeetCode的测试(这水平令人心塞哇)。
路径引用问题 (核心错误)
在self.result.append(self.curPath)中直接添加curPath的引用,而非拷贝。由于Python列表是可变对象,后续的pop()操作会修改已添加到结果中的路径,导致最终结果全部为空或错误。
修正方法:使用self.curPath.copy()或list(self.curPath)创建副本
DS给出的题解如下,它还贴心地加上了注释(doge):
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root:
return []
self.result = []
self.curPath = [root.val] # 初始化路径包含根节点值
self.count = targetSum - root.val # 计算初始差值
self.traverse(root)
return self.result
def traverse(self, node):
# 到达叶子节点且路径和等于目标值时,保存路径副本
if not node.left and not node.right and self.count == 0:
self.result.append(self.curPath.copy()) # 关键修正:深拷贝路径
# 递归处理左子树
if node.left:
self.curPath.append(node.left.val)
self.count -= node.left.val
self.traverse(node.left)
self.curPath.pop() # 回溯
self.count += node.left.val
# 递归处理右子树
if node.right:
self.curPath.append(node.right.val)
self.count -= node.right.val
self.traverse(node.right)
self.curPath.pop() # 回溯
self.count += node.right.val
建议大家以后也可以尝试用DS改bug(可以延伸到所有需要及时反馈的场合),像和老师交谈一样,效率快很多。
这里再浅浅谈下递归的感受:
所谓递归,就是利用函数的嵌套(通常是深层嵌套),使得从数据结构的底层自下往上开始遍历,所以递归有终止条件Base Case、递归调用Recusive Call和当前层处理逻辑Current Step Logic三要素。
还有就是回溯:
看看下面这段代码,“撤销”一词非常形象!从遍历的角度上看,只有到达底层叶子节点且路径长度符合要求时递归函数才会返回结果到self.result,除此之外的搜索后会自动撤销进行向上回溯。
# 递归处理左子树
if node.left:
self.curPath.append(node.left.val)
self.count -= node.left.val
self.traverse(node.left)
self.curPath.pop() # 回溯关键步骤,当层递归返回后撤销本层操作
self.count += node.left.val
106.从中序与后序遍历序列构造二叉树
逻辑上还是比较复杂,先记录下第一次AC的题解,二刷的时候肯定要尝试更加精简。
# 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]:
if inorder == []: return []
self.biTree = self.travseBetween(inorder, postorder)
return self.biTree
def travseBetween(self, cur_inorder, cur_postorder):
if cur_inorder == []: return
midNode_val = cur_postorder[-1]
midNode = TreeNode(midNode_val)
i = 0
while i < len(cur_inorder):
if midNode_val != cur_inorder[i]:
i += 1
else:
left_inorder = cur_inorder[:i]
left_postorder = cur_postorder[:i]
right_inorder = cur_inorder[i+1:]
right_postorder = cur_postorder[i:-1]
midNode.left = self.travseBetween(left_inorder, left_postorder)
midNode.right = self.travseBetween(right_inorder, right_postorder)
break
return midNode
谈点题外话
做这道题的时候,一开始脑子想的是:“如果可以更加中序和后序数组求得构造出二叉树,肯定可以使用数学公式得到某种表达式”。现在认识到,不少算法设计依赖过程性逻辑、启发式规则或复杂状态转移,需通过步骤描述(需要代码实现)而非单一公式,比如本题。