leetcode树之二叉树的创建

前言

前面介绍了二叉树的遍历,这次我们来考虑一下二叉树的创建,给定前中后序遍历的结果,这个时候要还原成二叉树应该怎么做呢?

下面来通过几道题来实际感受一下吧!

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]
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

溪语流沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值