本文主要简述在leecode中解二叉树类型题目使用的常见方法,每种解题方法通过leetcode例题进行分析。不妥之处,欢迎指正。
一、利用递归遍历解题
二叉树递归解题重点在于利用思考根节点、左子树、右子数的关系,其余工作交给遍历处理,二叉树基本框架:
if root == None:
return None
# 前序遍历:do_something
traverseTree(root.left)
# 中序遍历:do_something
traverseTree(root.right)
# 后序遍历:do_something
题目1:二叉树的深度(leetcode)
思路: 思考题干中树的深度和根节点、左子树、右子数的关系,不难得出关系:树的深度=max(左子树深度,右子树深度)+1,利用框架得出如下代码:
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
# 求左子树深度
ldepth = self.maxDepth(root.left)
# 求右子树深度
rdepth = self.maxDepth(root.right)
# 树的深度
depth = max(ldepth,rdepth) + 1
return depth
题目2:二叉树的直径(leetcode)
思路: 任何直径路径必然存在一个节点,将路径分隔为左右子数(左右子树可为空)。对于以该节点为根节点的子树而言,树直径路径的长度可转化为树的深度,公式:树的直径=左子树深度+右子树深度。所以求出所有节点的左右子树深度,得出所有子树的直径,最大者为树的直径。如下图所示:
选中节点左子树深度为3,选中节点右子树深度也为3,因此该选中子树的直径为3+3=6。
def diameterOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.ans = 0
def depth(node):
if not node:
return 0
L = depth(node.left)
R = depth(node.right)
self.ans = max(self.ans,L+R)
return max(L,R)+1
depth(root)
return self.ans
1、特殊场景-两个节点同时遍历
两个节点同时遍历主要针对需要同时处理两个节点的问题,重点在于思考两个节点之间需要做哪些操作,同时需要如何遍历后两个子节点。基本框架如下:
if root == None:
return True
return self.helper(root.left,root.right)
def helper(self,node1,node2):
if 达到边界条件:
return
# 对两个节点进行某些操作do_something()
self.helper(node1.子节点,node2.子节点)
题目3:对称二叉树(leetcode)
思路: 两个节点同时遍历有时是解决某类特殊问题的重要法宝,如对称二叉树,需判断每一层的对称节点值是否一致,很明显需要同时遍历两个节点,因此在函数签名中,需要设置两个入参并同时递归,该方式可以将特定关系的两个节点全部遍历一次。
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if root == None:
return True
return self.helper(root.left,root.right)
def helper(self,node1,node2):
# 若两个节点非空且数值相同,则用递归判断两个节点的子节点是否相同
if node1 != None and node2 != None and node1.val == node2.val:
return self.helper(node1.left,node2.right) and self.helper(node1.right,node2.left)
# 边界条件,若两个节点均为空,则返回True
if node1 == None and node2 == None:
return True
return False
题目4:填充每个节点的下一右侧节点指针(leetcode)
该题可以用过递归遍历,也可以通过层次遍历解答。
思路: 本次分析递归遍历。根据观察可知,需要将同一层次的两个节点依次关联,因此可以考虑遍历的特殊场景-两个节点同时遍历。构建辅助函数connectTwoNodes,该函数接收两个节点入参,完成将两节点串联的功能,边界条件为第一个节点为空。通过递归调用可以将所有的节点按要求串联起来。
def connect(self, root):
"""
:type root: Node
:rtype: Node
"""
if root == None:
return root
root.next = None
self.connectTwoNodes(root.left,root.right)
return root
# 连接两个节点
def connectTwoNodes(self,firstNode,secondNode):
# 子节点为空,直接返回
if firstNode == None:
return None
firstNode.next = secondNode
secondNode.next = None
# 将第一个节点的左子节点和第一个节点的右子节点连接
self.connectTwoNodes(firstNode.left,firstNode.right)
# 将第一个节点的右子节点和第二个节点的左子节点连接
self.connectTwoNodes(firstNode.right,secondNode.left)
# 将第二个节点的左子节点和第二个节点的右子节点连接
self.connectTwoNodes(secondNode.left,secondNode.right)
return None
2、特殊场景-构建二叉树
二叉树的增删等行为会影响二叉树的节点左右指针,所以相比于遍历需要注意对二叉树节点左右指针的赋值。
题目5:从前序与中序遍历序列构造二叉树(leetcode)
思路: 根据遍历的性质可以得知:可通过前序遍历确定二叉树的根节点,二叉树的根节点进而将中序遍历划分左边部分构建左子树、右边部分构建右子树,最后通过递归将整棵二叉树构建完毕,注意构建过程中需要将根节点的左右指针进行赋值。
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
if len(preorder) == 0:
return None
# 从前序遍历序列中取出树的根节点
root = TreeNode(preorder[0])
# 根据根节点在中序遍历中的位置划分左子树和右子树
index = inorder.index(preorder[0])
# 递归构建左子树,节点指针赋值
root.left = self.buildTree(preorder[1:index+1],inorder[0:index])
# 递归构建右子树,节点指针赋值
root.right = self.buildTree(preorder[index+1:],inorder[index+1:])
return root
二、利用宽度优先遍历解题
先看一下宽度优先搜索(BFS),如下图遍历方式:
BFS通常借队列数据结构实现,框架如下:
if root == None:
return None
# 使用队列,实现节点FIFO
lst = []
lst.append(root)
while lst != []:
node = lst.pop(0)
# 遍历,do_something
if node.left != None:
lst.append(node.left)
if node.right != None:
lst.append(node.right)
但通常我们解题过程中需要知道每个节点所处的层次关系,BFS无法区分各节点层次关系。所以需要在BFS遍历过程中加入计数器,记录每个层次的节点数量,从而形成层次遍历。框架如下:
if root == None:
return None
# 使用队列,实现节点FIFO
lst = []
lst.append(root)
while lst != []:
# 每次到达新行时便计算此行的节点数n
n = len(lst)
for i in range(n):
node = lst.pop(0)
# 遍历,do_something
if node.left != None:
lst.append(node.left)
if node.right != None:
lst.append(node.right)
题目6:二叉树的层序遍历(leetcode)
思路: 直接上层次遍历框架即可
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if root == None:
return []
lst = []
ans = []
lst.append(root)
while lst != []:
n = len(lst)
# 每一行新建一个list
ans.append([])
for i in range(n):
node = lst.pop(0)
ans[-1].append(node.val)
if node.left != None:
lst.append(node.left)
if node.right != None:
lst.append(node.right)
return ans
题目7: 填充每个节点的下一个右侧节点指针(leetcode)
思路: 该题将问题即是利用层次遍历,将遍历过程中同一层节点前后关联在一起,需要注意的是除每一行最后一个节点外,其余节点均与下一同行节点关联2-3,4-5,5-6,6-7。如下解法也可以解决leetcode-117题目《填充每个节点的下一个右侧节点指针 II》。该题还有递归遍历解法,详见题目4。
def connect(self, root):
"""
:type root: Node
:rtype: Node
"""
if root == None:
return None
lst = []
lst.append(root)
while lst != []:
n = len(lst)
for i in range(n):
node = lst.pop(0)
if node.left != None:
lst.append(node.left)
if node.right != None:
lst.append(node.right)
#若该节点为该行最后一个节点,则无需设置next指针
if i == n-1:
continue
# 串联同一行的下一节点
node.next = lst[0]
return root