目录
一、运用递归解决树的问题 (Solve Tree Problems by Recursion)
1.1 “自顶向下” 的解决方案 (Top-Down Solution)
1.2 “自底向上” 的解决方案 (Bottom-Up Solution)
一、运用递归解决树的问题 (Solve Tree Problems by Recursion)
前面的章节中,已经介绍了如何利用递归求解树的遍历。 递归 是解决树的相关问题最有效而常用的方法之一。
已知,树能够以递归的方式定义为一个节点 (根节点),它包括一个元素值和一个指向其他节点指针的列表。递归也是树的特性之一,因此许多树问题可以通过递归解决。对于每个递归层级,只能关注单个节点内的问题,并通过递归调用函数来解决其子节点问题。
通常,可以通过 “自顶向下” 或 “自底向上” 的递归来解决树问题。
1.1 “自顶向下” 的解决方案 (Top-Down Solution)
1. return specific value for null node
2. update the answer if needed // anwer <-- params
3. left_ans = top_down(root.left, left_params) // left_params <-- root.val, params
4. right_ans = top_down(root.right, right_params) // right_params <-- root.val, params
5. return the answer if needed // answer <-- left_ans, right_ans
1. return if root is null
2. if root is a leaf node:
3. answer = max(answer, depth) // update the answer if needed
4. maximum_depth(root.left, depth + 1) // call the function recursively for left child
5. maximum_depth(root.right, depth + 1) // call the function recursively for right child
// C++ implementation
int answer; // don't forget to initialize answer before call maximum_depth
void maximum_depth(TreeNode* root, int depth) {
if (!root) {
return;
}
if (!root->left && !root->right) {
answer = max(answer, depth);
}
maximum_depth(root->left, depth + 1);
maximum_depth(root->right, depth + 1);
}
# Python implementation
answer = 0 # don't forget to initialize answer before call maximum_depth
def maximum_depth(root, depth):
nonlocal answer ## 如果是全局变量则声明 global answer ##
if not root: # 空节点
return
if (not root.left) and (not root.right): # 外部节点 / 叶子节点
answer = max(answer, depth)
maximum_depth(root.left, depth+1)
maximum_depth(root.right, depth+1)
1.2 “自底向上” 的解决方案 (Bottom-Up Solution)
1. return specific value for null node
2. left_ans = bottom_up(root.left) // call function recursively for left child
3. right_ans = bottom_up(root.right) // call function recursively for right child
4. return answers // answer <-- left_ans, right_ans, root.val
1. return 0 if root is null // return 0 for null node
2. left_depth = maximum_depth(root.left)
3. right_depth = maximum_depth(root.right)
4. return max(left_depth, right_depth) + 1 // return depth of the subtree rooted at root
// C++ implementation
int maximum_depth(TreeNode* root) {
if (!root) {
return 0; // return 0 for null node
}
int left_depth = maximum_depth(root->left);
int right_depth = maximum_depth(root->right);
return max(left_depth, right_depth) + 1; // return depth of the subtree rooted at root
}
# Python implementation
def maximum_depth(root):
if not root: # return 0 for None node
return 0
left_depth = maximum_depth(root.left)
right_depth = maximum_depth(root.right)
return max(left_depth, right_depth) + 1 # return depth of the subtree rooted at root
1.3 总结
了解递归并利用递归解决问题并不容易。当遇到树问题时,请先思考一下两个问题:
你能确定一些参数,从该节点自身解决出发寻找答案吗?
- 你可以使用这些参数和节点本身的元素值来决定什么应该是传递给它子节点的参数吗?
- 若答案都是肯定的,那么请尝试用 “
自顶向下
” 的递归解决此问题。
换言之,对于树中的任意一节点,若你知道它子节点的答案,你能计算出该节点的答案吗? 如果答案是肯定的,那么 “自底向上
” 的递归可能是一个不错的解决方法。
在接下来的章节中将提供几个经典例题,以便于更好地理解树的结构和递归。
二、二叉树的最大深度
2.1 题目要求
2.2 解决过程
个人实现
法一:自顶向下。
特别注意局部变量 answer 要在嵌套函数体中进行 nonlocal 声明 (一般放在函数体开头),否则会报错:
UnboundLocalError: local variable 'answer' referenced before assignment
即便是 global 声明也不行,毕竟 answer 仍为函数体内的局部变量而非全局变量,报错信息:
NameError: name 'answer' is not defined
2020/07/15 - 75.18% - 次优 - 尾递归
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
answer = 0 # don't forget to initialize answer before call maximum_depth
def maximum_depth(root, depth):
nonlocal answer # global 关键字对应全局变量 / nonlocal 关键字对应非局部变量
if not root: # 空节点
return
if (not root.left) and (not root.right): # 外部节点 / 叶子节点
answer = max(answer, depth)
maximum_depth(root.left, depth+1)
maximum_depth(root.right, depth+1)
maximum_depth(root, 1)
return answer
法二:自底向上。
2020/07/15 - 90.11% - 优且简洁 - 线性递归
class Solution:
def maxDepth(self, root: TreeNode) -> int:
def maximum_depth(root):
if not root: # return 0 for None node
return 0
left_depth = maximum_depth(root.left)
right_depth = maximum_depth(root.right)
return max(left_depth, right_depth) + 1 # return depth of the subtree rooted at root
answer = maximum_depth(root)
return answer
法三:自底向上改。
2020/07/15 - 96.95% - 最优且最简洁
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
else:
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) # 神来之笔
官方解答与说明
# Python implementation
class TreeNode(object):
""" Definition of a binary tree node."""
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if root is None:
return 0
else:
left_height = self.maxDepth(root.left)
right_height = self.maxDepth(root.right)
return max(left_height, right_height) + 1
class Solution:
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
stack = []
if root is not None:
stack.append((1, root))
depth = 0
while stack != []:
current_depth, root = stack.pop()
if root is not None:
depth = max(depth, current_depth)
stack.append((current_depth + 1, root.left))
stack.append((current_depth + 1, root.right))
return depth
参考文献
三、对称二叉树
3.1 题目要求
3.2 解决过程
官方实现与说明
// C++ implementation
class Solution {
public:
bool check(TreeNode *p, TreeNode *q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
# Python implementation
class Solution:
def check(self, p, q):
""" DFS """
# 当前这对节点存在三种情况, 分类讨论
if (not p) and (not q): # 没有子节点
return True
elif (not p) or (not q): # 只有一个子节点
return False
else: # 同时有左右子节点
# 很特别而重要的关键句, 但凡存在一个 False 最终结果就是 False
return (p.val == q.val) and self.check(p.left, q.right) and self.check(p.right, q.left)
def isSymmetric(self, root: TreeNode):
return self.check(root, root)
2020/07/15 - 51.04%
// C++ implementation
class Solution {
public:
bool check(TreeNode *u, TreeNode *v) {
queue <TreeNode*> q;
q.push(u); q.push(v);
while (!q.empty()) {
u = q.front(); q.pop();
v = q.front(); q.pop();
if (!u && !v) continue;
if ((!u || !v) || (u->val != v->val)) return false;
q.push(u->left);
q.push(v->right);
q.push(u->right);
q.push(v->left);
}
return true;
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
# Python implementation
class Solution:
def check(self, u, v):
queue = [] # 模拟队列
queue.append(u) # 初始节点依次入队
queue.append(v)
while len(queue) != 0:
u = queue.pop(0) # 待比较节点依次出队
v = queue.pop(0)
if (not u) and (not v): # 叶子节点没有子节点,
continue
if (not u) or (not v) or (u.val != v.val): # 子节点缺失或值不等
return False
queue.append(u.left) # 下一轮迭代待比较节点依次入队
queue.append(v.right)
queue.append(u.right)
queue.append(v.left)
return True
def isSymmetric(self, root: TreeNode):
return self.check(root, root)
2020/07/15 - 51.07%
法二改:使用内置容器模块 collections 的双端队列 deque 来取代 list 模拟的单向队列:
class Solution:
def check(self, u, v):
q = collections.deque() # 辅助双端队列
q.append(u) # 初始节点依次入队
q.append(v)
while q:
u = q.popleft() # 待比较节点依次出队
v = q.popleft()
if (not u) and (not v): # 叶子节点没有子节点,
continue
if (not u) or (not v) or (u.val != v.val): # 子节点缺失或值不等
return False
q.append(u.left) # 下一轮迭代待比较节点依次入队
q.append(v.right)
q.append(u.right)
q.append(v.left)
return True
def isSymmetric(self, root: TreeNode):
return self.check(root, root)
2020/07/15 - 74.29%
参考文献
https://leetcode-cn.com/problems/symmetric-tree/solution/dui-cheng-er-cha-shu-by-leetcode-solution/
四、路径总和
4.1 题目要求
4.2 实现过程
测试用例
[5,4,8,11,null,13,4,7,2,null,null,null,1]
22
[]
1
[1]
1
个人实现
法一:尾递归。实现难点在于,遍历所有可能的路径,到达叶节点 (base case) 后必须返回,若第一次未发现就 return False 将阻止其他路径的遍历。因此,使用一个 flag 标志标识目标路径发现状态,每次遍历令当前和 summ 减去当前根节点值 root.val。一旦发现目标路径 (在叶节点处满足 summ=0) 就令 flag=True,并在函数最后 return flag。
由于最坏情况需要遍历整棵树所有节点,空间复杂度 O(1),时间复杂度 O(n)。
2020/07/27 - 99.58% (40ms) - 最优
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root: # 特殊情况处理 - 空树
return False
flag = False # 目标路径存在状态, 默认不存在
def recur(root, summ):
if (not root.left) and (not root.right): # base case - leaf node
if summ == 0:
nonlocal flag # 一定要声明, 不然这里的 flag 就是局部变量了
flag = True # 一旦发现目标路径就置为 True
return # 不论有无发现, 到了叶节点后均需返回
if root.left:
recur(root.left, summ-root.left.val) # 往左
if root.right:
recur(root.right, summ-root.right.val) # 往右
recur(root, sum-root.val)
return flag
官方实现与说明
# Python implementation
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
que_node = collections.deque([root]) # 存储路径
que_val = collections.deque([root.val]) # 存储节点和
while que_node:
now = que_node.popleft() # 当前节点出队
temp = que_val.popleft() # 当前节点和出队
if (not now.left) and (not now.right):
if temp == sum:
return True # 叶子节点处和符合
continue
if now.left: # 往左
que_node.append(now.left)
que_val.append(now.left.val + temp)
if now.right: # 往右
que_node.append(now.right)
que_val.append(now.right.val + temp)
return False
2020/07/27 - 83.64% (52ms)
## 很强, 用各结果的 or 巧妙地解决个人实现中遇到的问题!!这也是一种 DFS 解法
# Python implementation
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return sum == root.val
return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val)
2020/07/27 - 99.95% - 最佳
参考文献
https://leetcode-cn.com/problems/path-sum/solution/lu-jing-zong-he-by-leetcode-solution/