前言
以下文字来自灵神视频下的评论
1. 如何思考二叉树相关问题?
- 不要一开始就陷入细节,而是思考整棵树与其左右子树的关系。
2. 为什么需要使用递归?
- 子问题和原问题是相似的,他们执行的代码也是相同的(类比循环),但是子问题需要把计算结果返回给上一级,这更适合用递归实现。
3. 为什么这样写就一定能算出正确答案?
- 由于子问题的规模比原问题小,不断“递”下去,总会有个尽头,即递归的边界条件 ( base case ),直接返回它的答案“归”;
- 类似于数学归纳法(多米诺骨牌),n=1时类似边界条件;n=m时类似往后任意一个节点
4. 计算机是怎么执行递归的?
- 当程序执行“递”动作时,计算机使用栈保存这个发出“递”动作的对象,程序不断“递”,计算机不断压栈,直到边界时,程序发生“归”动作,正好将执行的答案“归”给栈顶元素,随后程序不断“归”,计算机不断出栈,直到返回原问题的答案,栈空。
5. 另一种递归思路
- 维护全局变量,使用二叉树遍历函数,不断更新全局变量最大值。
1. Leetcode 104. 二叉树的最大深度 https://leetcode.cn/problems/maximum-depth-of-binary-tree/solutions/2010612/kan-wan-zhe-ge-shi-pin-rang-ni-dui-di-gu-44uz/
解题思路:
把问题拆小! 二叉树的最大深度是什么? 答案是,左右子树的最大深度取max +1。
边界如何处理?
很简单! 根节点为空时返回0即可!
# 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 maxDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
# 左子树最大深度
left_m = self.maxDepth(root.left)
# 右子树最大深度
right_m = self.maxDepth(root.right)
return max(left_m, right_m) + 1
2. Leetcode111. 二叉树的最小深度 https://leetcode.cn/problems/minimum-depth-of-binary-tree/
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
解题思路:
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
首先,将问题拆分。 求二叉树的最小深度就是 求左右子树的最小深度,取min后+1。
然后,处理边界, if root is None: return inf 空节点不是我们想要的内容
if root.left is None and root.right is None: return 1 叶子节点才是我们递归的真正终点,返回它本身的高度1。
此外,上述内容应该定义在函数中。 因为传入的树可能是一颗空树,所以 if root is None: return 0
# 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 minDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
def minD(root):
if root is None:
return inf
if root.left is None and root.right is None:
return 1
left_m = minD(root.left)
right_m = minD(root.right)
return min(left_m, right_m) + 1
return minD(root)
3. Leetcode 112. 路径总和 https://leetcode.cn/problems/path-sum/
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
解题思路:
首先,拆解问题。 求root->叶子节点的路径,要求路径之和 == targetsum。
求root->叶子节点的路径 ==> 左右子树是否存在到叶子的路径,要求路径之和 == targetsum - root.val。
然后,处理边界。
if root is None: return False 如果传进来是空树,那么不存在路径,return False
if root.left is None and root.right is None: return targetsum == root.val
如果是叶子节点,那么只需要判断一下 经过多次减法的targetsum 和 root.val 是否相等
以下是代码
# 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
if root.left is None and root.right is None:
return targetSum == root.val
return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)
4. Leetcode 113. 路径总和 II https://leetcode.cn/problems/path-sum-ii/
解题思路:
和上题的思路是类似的,只不过我们要额外定义两个数组,path和result。 前者用来存经过的路径,后者是如果满足路径和==targetSum,就把path加入我们的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 pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
res, path = [], []
def getRes(root, targetSum):
if root is None:
return
targetSum -= root.val
path.append(root.val)
if not root.left and not root.right and targetSum == 0:
res.append(path.copy())
getRes(root.left, targetSum)
getRes(root.right, targetSum)
path.pop()
getRes(root, targetSum)
return res
5. Leetcode 129. 求根节点到叶节点数字之和 https://leetcode.cn/problems/sum-root-to-leaf-numbers/
解题思路:
我的笨比思路,通过前两道题我学会了如何获取从头节点到叶子节点的列表。 所以这道题我的做法就是拿到从头节点到叶子节点所有路径,然后全部变成字符串,再转换成数字,然后求和。
以下是代码
# 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 sumNumbers(self, root: Optional[TreeNode]) -> int:
res, path = [], []
def getRes(root):
if root is None:
return
path.append(str(root.val))
if not root.left and not root.right:
res.append(path.copy())
getRes(root.left)
getRes(root.right)
path.pop()
getRes(root)
s = 0
for i in res:
p = ''.join(i)
s += int(p)
return s
6. Leetcode 257. 二叉树的所有路径 https://leetcode.cn/problems/binary-tree-paths/
解题思路:
easy题直接秒0 0, 直接复制上一题的代码稍作修改即可。
# 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 binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
res, path = [], []
def getRes(root):
if root is None:
return
path.append(str(root.val))
if not root.left and not root.right:
res.append('->'.join(path.copy()))
getRes(root.left)
getRes(root.right)
path.pop()
getRes(root)
return res
7. Leetcode 1448. 统计二叉树中好节点的数目 https://leetcode.cn/problems/count-good-nodes-in-binary-tree/
解题思路:
这几题真是顺下来的,灵神伟大,无需多言。 越做思路越清晰!
定义path列表,用来存放经过的全部节点。
if 当前节点的值 >= path 列表中的所有值,那么!cnt += 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 goodNodes(self, root: TreeNode) -> int:
if root is None:
return 0
path = []
cnt = 0
def func(root):
nonlocal cnt
if root is None:
return
path.append(root.val)
if all(root.val >= x for x in path):
cnt += 1
func(root.left)
func(root.right)
path.pop()
func(root)
return cnt
8. Leetcode987. 二叉树的垂序遍历 https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/
给你二叉树的根结点 root
,请你设计算法计算二叉树的 垂序遍历 序列。
对位于 (row, col)
的每个结点而言,其左右子结点分别位于 (row + 1, col - 1)
和 (row + 1, col + 1)
。树的根结点位于 (0, 0)
。
二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有结点,形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个结点,则按结点的值从小到大进行排序。
返回二叉树的 垂序遍历 序列。
解题思路:
思路是有的!这道题要求按列遍历二叉树,那么思路就是用字典来保存col:[]。
即遍历节点的时候将相同列的值存放到一起。 此外还要求从上到下遍历,这意味着我们也要把行的信息保存下来。
[(3,2), [0,1],(0,-2),(1,4),(2,5)] 假设我们存在这样的列
首先要按行排序,当存在同行同列的元素时再按val排序。
补充知识
defaultdict
defaultdict是Python内建dict类的一个子类,第一个参数为default_factory
属性提供初始值,默认为None。它覆盖一个方法并添加一个可写实例变量。它的其他功能与dict相同,但会为一个不存在的键提供默认值,从而避免KeyError异常。
以下是代码
# 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 verticalTraversal(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return
col, row = 0, 0
dic = defaultdict(list)
def func(root, row, col):
if root is None:
return
dic[col].append((row, root.val))
func(root.left, row + 1, col - 1)
func(root.right, row + 1, col + 1)
func(root, row, col)
ans = []
# 按键排序 也就是按列排序
for _, z in sorted(dic.items()):
# 按row排序,row相同则按val排序
z.sort()
ans.append([x for _, x in z])
return ans