1、二叉树问题的递归解法最难的地方在于递归三要素中的提取重复的逻辑,缩小问题规模,即递归函数内部的操作,深度优先遍历的前、中、后序遍历是二叉树最基本的题目,其对应的重复逻辑也是最简单的
2、这种重复逻辑的归纳主要得益于二叉树结构本身:对根节点的操作与对其左右子树的操作是一致的,这就是一种非常自然的递归过程模拟,并在一次次对左右子树的重复操作过程中,将一个大规模问题一点点解决完
3、在重复逻辑的代码实现的递归函数调用代码行,要注意区分黑盒函数调用和内部实现逻辑,有点类似于链表题型总结时的cur.next分别放置于等号左右两侧时倾向于采用哪种理解(给当前节点的指针赋值或操作下个节点),虽然它们都是一个意思,但按照黑盒函数调用而不是考虑内部实现逻辑能减少思路混乱
4、这些简单重复逻辑的递归的问题,就是让我们先入门和体会二叉树里的递归解法,并在此基础上,深入掌握更复杂的题型,不断练习提取重复的逻辑
递归遍历:重复逻辑为 对每个二叉树都进行获取根节点值、访问左右子树的操作
'''
144. 二叉树的前序遍历
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
思路1、递归遍历:重复逻辑为 对每个二叉树都进行获取根节点值、访问左右子树的操作
思路2、迭代遍历:用栈实现,入栈时先加入右子树,再加入左子树
'''
class Solution:
# 1、递归实现方式——清晰直观的根左右遍历顺序
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 1、确定函数的参数和返回值
def traversal(node: Optional[TreeNode], result: List[int]):
# 2、终止条件
if node == None:
return
# 3、确定递归的单次操作
result.append(node.val) # 根
traversal(node.left, result) # 左
traversal(node.right, result) # 右
result = []
traversal(root, result)
return result
# 2、迭代循环实现方式:用栈实现,入栈时先加入右孩子,再加入左孩子——根左右遍历顺序被隐藏起来了
def preorderTraversalIteration(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
stk = [root]
result = []
while len(stk) > 0:
node = stk.pop() # 中
result.append(node.val)
if node.right != None:
stk.append(node.right) # 先右后左保证出栈处理时是 先左后右 # 右(空节点不入栈)
if node.left != None:
stk.append(node.left) # 左(空节点不入栈)
return result
递归遍历:重复逻辑为 对每个二叉树都进行访问左子树、获取根节点值、访问右子树的操作
'''
94. 二叉树的中序遍历
给你二叉树的根节点 root ,返回它节点值的 中序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
思路1、递归遍历:重复逻辑为 对每个二叉树都进行访问左子树、获取根节点值、访问右子树的操作
思路2、迭代遍历:由于访问节点(从根节点开始遍历)的顺序与处理节点(加入result中)的顺序不一致,需要将遍历到的节点先用栈存起来,当访问节点cur标记为
None时,才需要出栈对节点进行处理
对比:从中序遍历的两种实现方式中可以看出,迭代遍历的方式没有递归的逻辑简单清晰
'''
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 1、确定函数的参数和返回值
def traversal(node: TreeNode, result: List[int]):
# 2、终止条件
if node == None:
return
# 3、确定递归的单次操作
traversal(node.left, result) # 左
result.append(node.val) # 根
traversal(node.right, result) # 右
result = []
traversal(root, result)
return result
def inorderTraversalIteration(self, root: Optional[TreeNode]) -> List[int]:
# 中序遍历:左中右——访问节点顺序和处理节点顺序不一致
# 额外加一个指针cur访问节点,继续用栈stk处理节点
# 情况1、树为空
if root == None:
return []
# 情况2、树不为空
stk = [] # 与前序、后序的迭代遍历不一致,即root节点不在栈初始化时入栈
cur = root
result = []
while cur != None or len(stk) > 0:
if cur != None: # 访问节点顺序和处理节点顺序不一致
stk.append(cur)
cur = cur.left # 左
else:
cur = stk.pop()
result.append(cur.val) # 中
cur = cur.right # 右
return result
递归遍历:重复逻辑为 对每个二叉树都进行访问左右子树、获取根节点值的操作
'''
145. 二叉树的后序遍历
给你二叉树的根节点 root ,返回它节点值的 后序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
思路1、递归遍历:重复逻辑为 对每个二叉树都进行访问左右子树、获取根节点值的操作
思路2、迭代遍历:用栈实现,入栈时先加入左子树,再加入右子树,实现“根右左”的遍历方式,再把结果翻转即可
'''
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 1、确定函数参数和返回值
def traversal(node: TreeNode, result: List[int]):
# 2、终止条件
if root == None:
return
# 3、确定单次递归的操作
traversal(root.left, result) # 左
traversal(root.right, result) # 右
result.append(root.val) # 根
result = []
traversal(root, result)
return result
def postorderTraversalIteration(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
stk = [root]
result = []
while len(stk) > 0:
node = stk.pop()
result.append(node.val) # 中
if node.left != None:
stk.append(node.left) # 先左后右进栈,出栈就为先右后左 # 左(空节点不入栈)
if node.right != None:
stk.append(node.right) # 右(空节点不入栈)
# # 将 根右左 翻转为 左右根
# left, right = 0, len(result) - 1
# while left < right:
# result[left], result[right] = result[right], result[left]
# left += 1
# right -= 1
return result[::-1]
递归遍历:重复逻辑为 对每个N叉树都进行获取根节点值、访问子树的操作
'''
589. N 叉树的前序遍历
给定一个 n叉树的根节点 root,返回 其节点值的 前序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
思路:递归遍历:重复逻辑为 对每个N叉树都进行获取根节点值、访问子树的操作
'''
class Solution:
def preorder(self, root: Optional[Node]) -> List[int]:
# 1、定义递归函数
def traversal(node: Optional[Node], result: List[int]):
# 2、终止条件
if node == None:
return
# 3、递归操作
result.append(node.val)
for child in node.children:
traversal(child, result)
result = []
traversal(root, result)
return result
递归遍历:重复逻辑为 对每个二叉树都进行访问子树、获取根节点值的操作
'''
590. N 叉树的后序遍历
给定一个 n叉树的根节点 root,返回 其节点值的 后序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[5,6,3,2,4,1]
思路:递归遍历:重复逻辑为 对每个N叉树都进行访问子树、获取根节点值的操作
'''
class Solution:
def postorder(self, root: Optional[Node]) -> List[int]:
# 1、定义递归函数
def traversal(node: Optional[Node], result: List[int]):
# 2、终止条件
if node == None:
return
# 3、递归操作
for child in node.children:
traversal(child, result)
result.append(node.val)
result = []
traversal(root, result)
return result
递归遍历:重复逻辑为 对每个二叉树都进行左右子树的交换、依次访问左右子树的操作
'''
226. 翻转二叉树
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
题眼:翻转二叉树
思路:前/后序遍历或层序遍历:获取值的操作修改为交换左右子树节点即可
'''
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: # 递归的思路
# 1、确定函数参数及返回值
def traversal(node: Optional[TreeNode]):
# 2、确定终止条件
if node == None:
return
# 3、确定单次递归的操作
node.left, node.right = node.right, node.left
traversal(node.left)
traversal(node.right)
traversal(root)
return root
def invertTreeIteration(self, root: Optional[TreeNode]) -> Optional[TreeNode]: # 迭代的思路
if root == None:
return None
stack = []
stack.append(root)
while len(stack) > 0:
node = stack.pop()
node.left, node.right = node.right, node.left
if node.right != None:
stack.append(node.right)
if node.left != None:
stack.append(node.left)
return root
思路1、前序迭代遍历:获取值的操作修改为判断是否为左叶子并加和即可
思路2、前序递归遍历:重复逻辑:二叉树根节点的左叶子之和等于 左右子树的左叶子之和
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if root == None: # 简单逻辑
return 0
else: # 重复逻辑
result = 0
if root.left != None and root.left.left == None and root.left.right == None:
result += root.left.val
return result + self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
def sumOfLeftLeavesIteration(self, root: Optional[TreeNode]) -> int:
if root == None:
return 0
stack = []
stack.append(root)
result = 0
while len(stack) > 0:
cur = stack.pop()
if cur.left != None and cur.left.left == None and cur.left.right == None: # 根
result += cur.left.val
if cur.right: # 右:先右后左
stack.append(cur.right)
if cur.left: # 左
stack.append(cur.left)
return result