递归三部曲
1. 参数和返回值:寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值,找到不符合的节点立刻返回,如果符合则遍历了整棵树
2. 终止条件:一般为空节点
3. 单层递归逻辑:遍历顺序
代码模板
- 前/中/后序遍历 递归法模板
- 前/中/后序遍历 迭代法模板
- 层次遍历 模板
经典错题
1. 验证二叉搜索树
二叉搜索树的定义:根节点的左子树节点的值都小于根节点的值,根节点的右子树节点的值都大于根节点的值
递归三部曲:
(1)参数:节点;返回值:bool类型一旦不符合直接返回
(2)终止条件:因为需要都小于和都大于,因此用一个遍历记录节点的值,一旦出现根节点的值大于后续节点,返回False,否则更新节点值
(3)单层递归逻辑:中序遍历(二叉搜索树是有序的),左右递归,中间处理
2. 二叉树展开为链表
解题思路:记录右子树,将左子树移到右子树的位置,再将右子树放在左子树的最右端
递归三部曲:
(1)参数:节点;返回值:空(需要遍历整棵树)
(2)终止条件:空节点
(3)单层递归逻辑:记录右子树,递归左子树,递归右子树(找到最下面的节点),将左子树移到右子树,然后将原来的右子树移到最右端
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
"""
Do not return anything, modify root in-place instead.
"""
#递归
#参数:节点;返回值:空
#终止条件:空
#单层递归逻辑:右子树插入到左子树最右端,左子树插入到右子树
if not root: return
right=root.right #记录右子树
self.flatten(root.left) #递归左子树
self.flatten(root.right) #递归右子树
root.left,root.right=None,root.left #最下面的左子树插入到右子树
node=root #记录根节点
while node.right:
node=node.right #记录根节点的右子树
node.right=right #将原来右子树插入到左子树的最右边
3. 从前序与中序遍历序列构造二叉树
解题思路:前序序列的第一个数为根节点,找到在中序序列的位置,划分为左右两个子树,找到左右两个子树的元素在前序序列的范围,递归上述过程
第一步:前序序列的第一个元素作为根节点
第二步:找到在中序序列的位置
第三步:分为左中序序列和右中序序列
第四步:分为左前序序列和右前序序列
第五步:递归左中序序列和左前序序列;递归右中序序列和右前序序列
第六步:返回根节点
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not preorder: return
#第一步,用前序序列第一个节点构造根节点
root=TreeNode(preorder[0])
#第二步,找到在中序序列的位置
mid=inorder.index(preorder[0])
#第三步,划分左右两个序列
left_inorder=inorder[:mid]
right_inorder=inorder[mid+1:]
#第四步,划分前序序列
left_preorder=preorder[1:len(left_inorder)+1]
right_preorder=preorder[len(left_inorder)+1:]
#第五步,递归
root.left=self.buildTree(left_preorder,left_inorder)
root.right=self.buildTree(right_preorder,right_inorder)
return root
4. 路径总和Ⅲ
递归三部曲:
(1)参数:节点、目标值;返回值:方案数
(2)终止条件:当搜索到节点为空,返回0个方案
(3)单层递归逻辑:节点非空,判断当前节点值是否等于目标值,若是,方案数+1
否则,目标值更新为targetsum-root.val,递归左子树,递归右子树,将返回的方案数累加
**注意,首先从根节点出发,其次考虑根节点的左孩子和右孩子出发
代码:
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
def rootSum(root, targetSum):
if root is None: #终止条件:节点为空,返回方案数为0
return 0
ret = 0
if root.val == targetSum: #如果节点非空,且节点值等于目标值,则方案数+1
ret += 1
ret += rootSum(root.left, targetSum - root.val) #递归左子树
ret += rootSum(root.right, targetSum - root.val) #递归右子树
return ret
if root is None:
return 0
ret = rootSum(root, targetSum)
ret += self.pathSum(root.left, targetSum)
ret += self.pathSum(root.right, targetSum)
return ret
5. 二叉树的最近公共祖先
解题思路:如果p和q位于根节点的两侧,返回根节点,如果p和q位于同一侧,则返回离根节点最近的节点
递归三部曲:
(1)参数:root,p,q;返回值:bool类型
(2)终止条件:当root==p或root==q或root为空时,返回节点本身
(3)单层递归逻辑:后序遍历,左(递归左子树)右(递归右子树)中(如果左右非空返回根节点;如果左空右非空,返回右;如果左非空右空,返回左)
代码:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
#参数:root,p,q;返回值:返回bool(节点)
#终止条件:遇到p/q立即返回,遇到空节点,返回节点本身
#单层递归逻辑:后序遍历,返回值的处理
if root == q or root == p or root is None:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left is not None and right is not None:
return root
if left is None and right is not None:
return right
elif left is not None and right is None:
return left
else:
return None
6. 二叉树中的最大路径和
解题思路:题目说明路径可以经过根节点,可以不经过根节点,选取那个路径和,主要看哪个更大,因此需要使用一个全局变量保存较大值(初始化为无穷小);在选择值的时候,保存非负数
递归三部曲:
(1)参数:节点;返回值:路径和
(2)终止条件:当节点为空,路径为0
(3)单层递归逻辑:后序遍历
left=左子树的路径和(递归左子树)
right=右子树的路径和(递归右子树)
中间节点的处理mid=left+right+root.val (经过中间节点的路径)
使用全局变量选取较大值,即max_sum = max(mid,max_sum)
返回值:单边路径和 root.val+max(left,right)
代码:
class Solution:
def __init__(self):
self.max_sum=float('-inf')
def maxPathSum(self, root: Optional[TreeNode]) -> int:
def traversal(root):
if not root:
return 0
#选择非负的
left=max(traversal(root.left),0) #单边值
right=max(traversal(root.right),0)
mid=root.val+left+right #经过中间节点
self.max_sum=max(mid,self.max_sum) #全局变量记录较大值
return root.val+max(left,right)#可以不经过中间节点
traversal(root)
return self.max_sum