前言
前面介绍了二叉树的遍历,这次我们来考虑一下二叉树的创建,给定前中后序遍历的结果,这个时候要还原成二叉树应该怎么做呢?
下面来通过几道题来实际感受一下吧!
105、从前序和中序序列构建二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
思路:
前序序列可以看做[root, [root左子树], [root右子树]],中序序列可以看做是[[root左子树], root, [root右子树]],从前序序列可找到父节点的位置,然后在中序序列可以根据root,找到左子树以及右子树的序列。
递归
采用分治的方式迭代创建左右子树
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder: return None
index = {e: i for i, e in enumerate(inorder)}
first = preorder[0]
root = TreeNode(preorder[0])
# 找到root节点位置,由于这边是不断更新preorder,root_index的位置代表着左子树的节点个数为root_index
# 那么左子树的前序序列就是preorder[1: root_index + 1]
# 右子树的前序序列就是preorder[root_index + 1:]
root_index = index[first]
root.left = self.buildTree(preorder[1:root_index+1], inorder[0:root_index])
root.right = self.buildTree(preorder[root_index+1:], inorder[root_index+1:])
return root
上面的方法,每次都要复制出一个新的前序和中序序列,还需要对中序进行字典进行下标的查找,耗费空间比较多。
可以使用下标的方式,不改变原有数组的情况下进行迭代
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder: return None
def dfs(pre_start, pre_end, in_start, in_end):
root_val = preorder[pre_start]
root = TreeNode(root_val)
# 如果前序序列的开始和结束位置一致,代表当前只存在一个结点,直接返回即可
if pre_start >= pre_end: return root
# 找到root在中序序列中的位置
root_in_index = index[root_val]
# 如果root的位置大于开始位置,说明该结点包含左子树
if root_in_index > in_start:
# 左子树的开始位置为上个位置的下一个位置
new_pre_start = pre_start + 1
# 左子树的结点数目为root_in_index - in_start
# 那么左子树的结束位置就是 开始位置 + 结点个数 - 1
new_pre_end = new_pre_start + root_in_index - in_start - 1
root.left = dfs(new_pre_start, new_pre_end, in_start, root_in_index - 1)
# 如果中序序列结束位置大于root的位置,说明该结点包含右子树
if in_end > root_in_index:
# 右子树的开始位置为,左子树起始位置 + 左子树结点个数
new_pre_start = pre_start + 1 + root_in_index - in_start
# 右子树的结点数目为 in_end - root_in_index
# 那么右子树的结束位置就是 右子树的开始位置 + 结点树 - 1
# new_pre_end = new_pre_start + in_end - root_in_index - 1
root.right = dfs(new_pre_start, pre_end, root_in_index + 1, in_end)
return root
n = len(preorder)
# 这里将中序序列的结点值和下标志进行映射
index = {e:i for i, e in enumerate(inorder)}
return dfs(0, n - 1, 0, n - 1)
迭代
通过对前序序列和中序序列进行分析,发现父节点和左子节点在前序序列和中序序列中出现的顺序是相反的。那什么时候顺序开始发生变化呢?从前序序列开始顺序往后找,如果出现值相同,那么就说明此时这个值就是左子树的最左端了,然后由于左子树在前序序列和中序序列的顺序是相反的,那么前序往前看,中序往后看,如果出现不同,那么就说明此时出现的值是右子树的值了。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder: return None
root = TreeNode(preorder[0])
# 维护栈用来查找左子树序列
stack = [root]
inIndex = 0
for i in range(1, len(preorder)):
preVal = preorder[i]
node = stack[-1]
# 如果栈顶元素和中序序列值一样,那么此时的结点就是左子树的最左端
# 此时出栈并且中序序列向后移动,直到出现的值不一样,此时preVal存储的是右子树的值
if node.val == inorder[inIndex]:
while stack and stack[-1].val == inorder[inIndex]:
node = stack.pop()
inIndex += 1
node.right = TreeNode(preVal)
stack.append(node.right)
# 如果不一样,那么一直将结点入栈即可
else:
node.left = TreeNode(preVal)
stack.append(node.left)
return root
106、从中序和后序遍历序列中构造二叉树
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例1:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
示例2:
输入:inorder = [-1], postorder = [-1]
输出:[-1]
思路:
按照上题的思路,中序序列是[[root左子树序列], root, [root右子树序列]],后序序列是[[左子树序列], [右子树序列], root],按照递归思路,可以通过逆序后序序列找到根节点,然后找到其左子树和右子树进行递归
递归
和上文一样,通过找到左子树的下标和右子树的下标通过递归构造树
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
def dfs(in_start, in_end, post_start, post_end):
root_val = postorder[post_end]
root = TreeNode(root_val)
if post_start >= post_end: return root
root_index = index[root_val]
if root_index > in_start:
new_post_end = post_start + root_index - in_start -1
root.left = dfs(in_start, root_index-1, post_start, new_post_end)
if in_end > root_index:
new_post_start = post_start + root_index - in_start
root.right = dfs(root_index+1, in_end, new_post_start, post_end - 1)
return root
index = {e:i for i, e in enumerate(inorder)}
n = len(postorder)
return dfs(0, n-1, 0, n-1)
迭代
通过寻找规律发现,逆序看后序和中序列,和上面的前序序列有相同的规律,这里我们只需要从后往前逆序操作即可
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
if not inorder: return None
root = TreeNode(postorder.pop())
stack = [root]
inIndex = len(inorder) - 1
while postorder:
preVal = postorder.pop()
node = stack[-1]
if node.val == inorder[inIndex]:
while stack and stack[-1].val == inorder[inIndex]:
stack.pop()
inIndex -= 1
node.right = TreeNode(preVal)
stack.append(node.right)
else:
node.left = TreeNode(preVal)
stack.append(node.left)
return root
889、根据前序和后序遍历序列构造二叉树
给定两个整数数组,preorder 和 postorder ,其中 preorder 是一个具有 无重复 值的二叉树的前序遍历,postorder 是同一棵树的后序遍历,重构并返回二叉树。
如果存在多个答案,您可以返回其中 任何 一个。
示例1:
输入:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]
输出:[1,2,3,4,5,6,7]
示例2:
输入: preorder = [1], postorder = [1]
输出: [1]
思路:
延续上面通过递归创建二叉树,但是由于通过前序和后序不能确定一个唯一的二叉树,这里只是提供一个思路。
首先可以确定先序遍历是[root, [root左子树序列], [root右子树序列]], 后序遍历是[[root左子树序列], [root右子树序列], root],这里采用优先创建左子树的方式创建整个二叉树
首先通过前序序列第一个元素确定根节点,然后紧跟着的是左子树的父元素,然后通过后序找到这个元素,在后序序列中,该元素左侧包含该元素,算作左子树的元素,右侧元素算作右子树,这样递归完成二叉树的创建。
class Solution:
def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:
def dfs(pre_start, pre_end, post_start, post_end):
# 取第一个结点作为根节点
root = TreeNode(preorder[pre_start])
if pre_start >= pre_end: return root
# 取下一个元素作为左子树的标记,在后序序列中为左子树序列的最后一个元素
left_index = index[preorder[pre_start + 1]]
# 如果left_index大于post_start,说明存在左子树
if left_index >= post_start:
# 这里重新计算左子树的开始和结束位置
# 开始位置直接是start + 1
new_pre_start = pre_start + 1
# 结束位置是在开始位置上 + 左子树序列的节点数 -1
# 左子树的结点树为 left_index - post_start + 1
# 然后最后得出下面的结果
new_pre_end = new_pre_start + left_index - post_start
root.left = dfs(new_pre_start, new_pre_end, post_start, left_index)
if post_end > left_index + 1:
# 这里右子树序列的开始位置应该是 左子树开始位置 + 左子树元素个数
# 左子树开始位置为 pre_start + 1
# 左子树元素个数 left_index - post_start + 1
# 所以最后计算结果如下
new_pre_start = pre_start + left_index - post_start + 2
# 结束位置直接就是pre_end即可,然后需要住一个是后序序列的结束位置是post_end - 1, post_end是根节点的位置
root.right = dfs(new_pre_start, pre_end, left_index+1, post_end-1)
return root
index = {e: i for i, e in enumerate(postorder)}
n = len(preorder)
return dfs(0, n-1, 0, n-1)
1028、从先序遍历还原二叉树
我们从二叉树的根节点 root 开始进行深度优先搜索。
在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。
如果节点只有一个子节点,那么保证该子节点为左子节点。
给出遍历输出 S,还原树并返回其根节点 root。
示例1:
输入:"1-2--3--4-5--6--7"
输出:[1,2,5,3,4,6,7]
示例2:
输入:"1-2--3---4-5--6---7"
输出:[1,2,5,3,null,6,null,4,null,7]
示例3:
输入:"1-401--349---90--88"
输出:[1,401,null,349,88,90]
思路:
字符串是二叉树的先序遍历,那么可以使用先序遍历构建二叉树,那么什么时候才知道到叶子节点呢?可以判断当前节点的深度和栈里的数据,如果栈里的数据和节点深度一致,说明当前在遍历左子树(注意这里的先序遍历表示不了左节点为空右节点有值的情况),如果栈的数据比节点深度大,那么需要进行出栈操作,直到栈的数据和节点深度一致。(由于根节点的深度是0,所以栈的最后一个元素的深度是栈的大小减一)
class Solution:
def recoverFromPreorder(self, traversal: str) -> Optional[TreeNode]:
n = len(traversal)
stack = []
i = 0
while i < n:
# 根据-计算元素的深度
level = 0
while traversal[i] == '-':
level += 1
i += 1
# 获取节点值,由于这里节点值可能不是一位数,如果遇到数字就继续往后叠加
start = i
while i < n and traversal[i].isdigit():
i += 1
val = int(traversal[start:i])
node = TreeNode(val)
# 如果节点深度和栈大小一致,那么将该节点添加到栈顶节点的左节点
# 否则弹出栈顶元素,知道节点深度和栈大小一致,这个时候应该把该节点放到栈顶节点的右节点上
if level == len(stack):
if stack:
stack[-1].left = node
else:
while level < len(stack):
stack.pop()
if stack:
stack[-1].right = node
stack.append(node)
# 返回栈底元素
return stack[0]