本篇全部集中在二叉树相关问题上,是参考东哥的思路进行的练习和思考。东哥有《labuladong 的算法小抄》以及宝藏微信公众号 labuladong,github 也有项目,自来水推荐购买和关注。
今天是递归本归~重要的事情念 3 遍:
之所以说今天是递归本归,是因为重点想强调对问题的拆分,化整为零、分治、通过小问题求解大问题的思想,而不是“自己调用自己”的表象。Day1 的内容中也有很多递归函数,但是却更强调通过遍历树来求解问题。
不用 traverse 辅助函数完成前中后序遍历:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 本函数接收一棵树的根节点(指针),返回该树的前序遍历
self.res = []
if not root: return self.res
self.res.append(root.val)
self.res += self.preorderTraversal(root.left)
self.res += self.preorderTraversal(root.right)
return self.res
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 本函数输入为一棵树的根节点(指针),返回该树的中序遍历
if not root: return []
res = []
res += self.inorderTraversal(root.left)
res.append(root.val)
res += self.inorderTraversal(root.right)
return res
def postorderTraversal(self, root: TreeNode) -> List[int]:
# 本函数输入为一棵树的根节点(指针),返回该树的后序遍历
if not root: return []
res = []
res += self.postorderTraversal(root.left)
res += self.postorderTraversal(root.right)
res.append(root.val)
return res
使用「大问题分解成子问题」的思维模式完成以下题目:
本函数输入为一棵树的根节点(指针),返回该树的最大深度。
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
本函数输入为一棵树的根节点(指针),返回该树的最小深度。
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
# 比最大深度问题多了一点需要额外考虑的,就是左子树为空或者右子树为空的时候
if not root.left:
return self.minDepth(root.right) + 1
elif not root.right:
return self.minDepth(root.left) + 1
return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
# 本函数输入为一棵树的根节点(指针),返回该树是否有和为 targetSum 的路径
if not root: return False
if not root.left and not root.right and targetSum == root.val: return True
return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)
一开始忘记给左子树赋值为 None 了。
但是:是不是这样递归写就不太满足题目要求了Do not return anything
,虽然返回的确实是最初树的根节点 root,但是确实通过了 return。如果不写 return,left = self.flatten(root.left)
的操作就完不成,后面也就不能 root.right = left
穿针引线了。
def flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
# 本函数接受一棵树的根节点(指针)为输入,返回该树对应的“链表”的根节点(指针)
if not root: return root
left = self.flatten(root.left)
right = self.flatten(root.right)
root.right = left
root.left = None
pt = root
while pt.right:
pt = pt.right
pt.right = right
return root
又想了以下,补了一个无 return 版本~
def flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
if root:
self.flatten(root.left)
self.flatten(root.right)
root.right = root.left
root.left = None
while root.right:
root = root.right
root.right = root.right
复习昨天遍历的思路完成以下题目:
因为就想严格套用回溯模板,所以 dfs 得到路径数字 list 再转 str。这里稍微折腾了一下,就是节点的左孩子和右孩子是否在“选择”列表中,应该是孩子节点不为空的时候才进行 dfs。
def binaryTreePaths(self, root: TreeNode) -> List[str]:
res = []
def dfs(track, root):
if not root: return
track.append(root.val)
if not root.left and not root.right:
res.append(track[:])
return
else:
for child in [root.left, root.right]:
if not child: continue
dfs(track, child)
track.pop()
return
dfs([], root)
return ['->'.join([str(n) for n in x]) for x in res]
额,上面 append 和 pop 写的不对称,我是不是其实不该用回溯啊,用回溯就意味着得先“放弃” root,补了一版单独判断 root + 回溯的。
def binaryTreePaths(self, root: TreeNode) -> List[str]:
res = []
def dfs(track, root):
if not root: return
if not root.left and not root.right:
res.append(track[:])
return
for child in [root.left, root.right]:
if not child: continue
track.append(child.val)
dfs(track, child)
track.pop()
return
if not root: return []
dfs([root.val], root)
return ['->'.join([str(n) for n in x]) for x in res]
所以还是补一个遍历好了,这样也不用单独写 root 了。
def binaryTreePaths(self, root: TreeNode) -> List[str]:
self.res = []
self.path = []
def helper(root):
if root is None: return
self.path.append(root.val)
if not root.left and not root.right:
self.res.append(self.path[:])
helper(root.left)
helper(root.right)
self.path.pop()
return
helper(root)
return ['->'.join([str(n) for n in x]) for x in self.res]
遍历思路:
def isLeafNode(self, root):
return not root.left and not root.right
def sumOfLeftLeaves(self, root: TreeNode) -> int:
# 通过遍历树来求解
self.res = 0
def helper(root):
if root is None:
return
if root.left:
if self.isLeafNode(root.left):
self.res += root.left.val
else:
helper(root.left)
helper(root.right)
return
helper(root)
return self.res
分治思路:
def isLeafNode(self, root):
return not root.left and not root.right
def sumOfLeftLeaves(self, root: TreeNode) -> int:
if not root: return 0
res = 0
if root.left:
if self.isLeafNode(root.left):
res += root.left.val
else:
res += self.sumOfLeftLeaves(root.left)
res += self.sumOfLeftLeaves(root.right)
return res
暴力枚举,方法可行仅供练习,但过不了 leetcode,会超时。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if amount == 0: return 0
# 维护两个变量记录深度和最小深度
self.min_num = amount + 1
self.cnt = 0
def dfs(amount):
if amount < min(coins): return
for coin in coins:
# 稍微剪枝
if coin > amount: continue
# 选择了该 coin
self.cnt += 1
# 如果正好凑够,更新最小深度
if amount - coin == 0:
self.min_num = min(self.min_num, self.cnt)
dfs(amount - coin)
# 撤销选择
self.cnt -= 1
return
dfs(amount)
return -1 if self.min_num == amount + 1 else self.min_num