前言
树的任何相关问题都离不开以下遍历框架:
同时二叉树的应用十分广泛,例如只要涉及递归相关问题,都可以抽象成二叉树问题,其求解离不开上述框架的使用、快速排序可以等价为前序遍历、以及归并排序可以等价为后序遍历等内容。
对于解决树的递归遍历框架的使用,其核心步骤可以总结为以下内容:
- 先搞清楚当前 root 节点「该做什么」以及「什么时候做」,然后根据函数定义递归调⽤⼦节点,递归调⽤会让⼦节点做相同的事情。
- 「该做什么」就是让你想清楚写什么代码能够实现题⽬想要的效果。
- 「什么时候做」,就是让你思考这段代码到底应该写在前序、中序还是后序遍历的代码位置上。
接下来我会用上述步骤对LeetCode中226、116和114三道算法题进行实操,加深我们对二叉树递归遍历框架思维的理解。
226. 翻转二叉树
解法:递归
「该做什么」:当前节点的左右子树相互交换。
「什么时候做」:前序和后后序遍历均可,前者是从上往下交换,后者是从下往上交换。
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return root
# 「该做什么」交换左右子树
root.left, root.right = root.right, root.left
# 「什么时候做」放在前序遍历位置
self.invertTree(root.left)
self.invertTree(root.right)
return root
116. 填充每个节点的下一个右侧节点指针
解法:递归
「该做什么」:将当前节点的左子树指向右子树。 node.left.next = node.right
「什么时候做」:前序遍历。
但该思路存在一个问题,即例子中5和6之间的链接关系无法实现,这时我们需要稍稍修改一下「该做什么」的内容,可以将其变为,接收两个节点输入,将节点1指向节点2,即node1.next = node2,并增加对中间节点对的遍历。
"""
# Definition for a Node.
class Node:
def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
self.val = val
self.left = left
self.right = right
self.next = next
"""
class Solution:
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
if not root:
return root
self.connectTwoNodes(root.left, root.right)
return root
def connectTwoNodes(self, node1, node2):
if not node1 or not node2:
return
# 「该做什么」接收两个节点输入,将节点1指向节点2
node1.next = node2
#「什么时候做」前序遍历
self.connectTwoNodes(node1.left, node1.right)
self.connectTwoNodes(node2.left, node2.right)
# 增加的中间节点对
self.connectTwoNodes(node1.right, node2.left)
解法2:层序遍历
上述解法存在些对中间节点的重复操作,可以考虑用层序遍历进行优化。
class Solution:
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
if not root:
return root
queue = collections.deque([])
queue.append(root)
while queue:
size = len(queue)
# 每一层进行操作
for i in range(size):
node = queue.popleft()
if i < size - 1:
node.next = queue[0]
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return root
114. 二叉树展开为链表
解法:递归
「该做什么」:将 root 的左右子树交换,然后将整个左⼦树接到右子树下方
「什么时候做」:需要将左右子树先拉平,再执行上述操作,因此是后序遍历。
# 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 flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
if not root:
return None
# 「什么时候做」:需要将左右子树先拉平,再执行上述操作,因此是后序遍历。
self.flatten(root.left)
self.flatten(root.right)
# 「该做什么」:将 root 的左右子树交换,然后将整个左⼦树接到右子树下方,左子树为None
root.left, root.right = root.right, root.left
p = root
while p.right != None:
p = p.right
p.right = root.left
root.left = None
return root
总结
对于解决树的递归遍历框架的使用,其核心步骤可以总结为以下内容:
- 先搞清楚当前 root 节点「该做什么」以及「什么时候做」,然后根据函数定义递归调⽤⼦节点,递归调⽤会让孩⼦节点做相同的事情。
- 「该做什么」就是让你想清楚写什么代码能够实现题⽬想要的效果。
- 「什么时候做」,就是让你思考这段代码到底应该写在前序、中序还是后序遍历的代码位置上。
只要明确了「该做什么」以及「什么时候做」,大部分递归问题都能够迎刃而解。