如果只是前中后序遍历的其中一种,是不可能唯一确定一个二叉树的,必须是其中两个的结合,由此便产生了三道题目,在这里可以全部秒杀。
需要记住的要点是:
前序(根左右)——第一个节点一定是根节点;
中序(左根右)——根节点左边一定是左子树,右边一定是右子树;
后序(左右根)——最后一个节点一定是根节点。
树节点类定义如下:
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right
105. 从前序与中序遍历序列构造二叉树
class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:if not preorder: # 判断空树return Noneroot = TreeNode(preorder[0]) # 根节点i = inorder.index(root.val) # 根节点下标为 i,左子树长度也已知为 iroot.left = self.buildTree(preorder[1:i+1], inorder[:i]) # 左子树root.right = self.buildTree(preorder[i+1:], inorder[i+1:]) # 右子树return root
从前序找到根节点,然后找到其在中序的位置 i,则该位置的左边 [:i] 为左子树,右边 [i+1:] 为右子树。因为已知左子树的长度为 i,则前序中的左子树为 [1:i+1],右子树为 [i+1:]。
优化的写法是,递归的时候传入指针而不是传入整个列表,所以列表不变而指针变,左子树的长度是两个指针之差:
class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):if preorder_left > preorder_right: # 判断空树return Noneroot = TreeNode(preorder[preorder_left]) # 根节点inorder_root = inorder.index(root.val) # 根节点下标size_left_subtree = inorder_root - inorder_left # 左子树长度root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1) # 左子树root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right) # 右子树return rootn = len(preorder)return myBuildTree(0, n - 1, 0, n - 1)
更加优化的写法,是用一个字典(哈希映射)记录下中序列表中数值与下标位置的关系,方便快速找到根的位置:
class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):if preorder_left > preorder_right: # 判断空树return Noneroot = TreeNode(preorder[preorder_left]) # 根节点inorder_root = index[root.val] # 根节点下标size_left_subtree = inorder_root - inorder_left # 左子树长度root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1) # 左子树root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right) # 右子树return rootn = len(preorder)# 构造哈希映射,帮助我们快速定位根节点index = {element: i for i, element in enumerate(inorder)}return myBuildTree(0, n - 1, 0, n - 1)
106. 从中序与后序遍历序列构造二叉树
class Solution:def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:if not inorder: # 判断空树return Noneroot = TreeNode(postorder[-1])i = inorder.index(root.val)root.left = self.buildTree(inorder[:i], postorder[:i])root.right = self.buildTree(inorder[i+1:], postorder[i:-1])return root
从后序找到根节点,然后找到其在中序的位置 i,则该位置的左边 [:i] 为左子树,右边 [i+1:] 为右子树。因为已知左子树的长度为 i,则后序中的左子树为 [:i],右子树为 [i:]。优化写法如下:
class Solution:def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:def mybuildTree(inorder_left:int, inorder_right:int, postorder_left:int, postorder_right:int):if inorder_left > inorder_right: # 判断空树return Noneroot = TreeNode(postorder[postorder_right]) # 根节点inorder_root = index[root.val]size_left_subtree = inorder_root - inorder_left # 左子树长度root.left = mybuildTree(inorder_left, inorder_root - 1, postorder_left, postorder_left + size_left_subtree - 1) # 左子树root.right = mybuildTree(inorder_root + 1, inorder_right, postorder_left + size_left_subtree, postorder_right - 1) # 右子树return rootn = len(inorder)# 构造哈希映射,帮助我们快速定位根节点index = {element: i for i, element in enumerate(inorder)}return mybuildTree(0, n-1, 0, n-1)
889. 根据前序和后序遍历构造二叉树
class Solution:def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:if not preorder: # 判断空树return Noneroot = TreeNode(preorder[0])if len(preorder) == 1: # 下面用到了pre[1],则必须确保长度大于1return rooti = postorder.index(preorder[1])root.left = self.constructFromPrePost(preorder[1:i+2], postorder[:i+1])root.right = self.constructFromPrePost(preorder[i+2:], postorder[i+1:-1])return root
从前序找到根节点,可知其下一个节点是左子节点(左子树的根节点),在后序中找到其位置 i,则该位置的左边 [:i+1] 为左子树,右边 [i+1:] 为右子树。因为已知左子树的长度为 i,则前序中的左子树为 [1:i+2],右子树为 [i+2:-1]。注意一个知识点:前序和后序不能唯一确定一棵二叉树,因为没有中序遍历无法确定左右部分,也就是无法分割。优化写法如下:
class Solution:def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:def myconstruct(preorder_left, preorder_right, postorder_left, postorder_right):if preorder_left > preorder_right:return Noneroot = TreeNode(preorder[preorder_left])if preorder_left == preorder_right:return rootpostorder_left_root = index[preorder[preorder_left+1]]size_left_subtree = postorder_left_root - postorder_leftroot.left = myconstruct(preorder_left + 1, preorder_left + size_left_subtree + 1, postorder_left,postorder_left + size_left_subtree)root.right = myconstruct(preorder_left + size_left_subtree + 2, preorder_right, postorder_left + size_left_subtree + 1, postorder_right - 1)return rootn = len(preorder)index = {element: i for i, element in enumerate(postorder)}return myconstruct(0, n-1, 0, n-1)
总结:三道题的关键,就是从前序或者后序遍历找到根节点,然后使用根节点或者根节点的左子节点来区分左右子树,完成递归。可以注意到,递归的时候:preorder 肯定没有第一个(根节点),inorder肯定没有第 i 个,postorder肯定没有最后一个;然后根据区分左右子树时用的是根节点还是它的左子节点,决定是 i 还是 i + 1。