后序+中序(前序+中序)重构树,严格O(N)算法

105/106. Construct Binary Tree from Inorder and Postorder(Preorder) Traversal

根据前序,中序或者后序,中序重构树。这里仅以后序+中序进行分析。文末给出前序的类似代码,原理一致。

Notes

  • 递归的难点在于递归函数的正确定义
  • 理解定义再理解代码非常简单,尤其是二叉树,对于左子树的递归,正确的理解是,左子树任务已经完成了,我需要做什么
  • 设计递归函数,可以从基元考虑(没有子节点,边界节点)和普遍情况考虑(即左子节点全部完成,右子节点全部完成),前者是自底向上,后者是自顶向下的思维。

Version 1

回到重构二叉树的问题,这其实是个常见的简单问题,重点就是把坐标细节搞定。

  • 新建当前节点
  • 找到左子树范围,重构左子树
  • 找到右子树范围,重构右子树
# version 1
# 递归构造
# 时间,每层时间和N,深度最多N,一般情况是logN,所以最差N^2,一般情况NlogN
# 额外空间,无
# Runtime: 356 ms, faster than 10.68% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 18.7 MB, less than 60.97% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        def rebuild(pl, ph, il, ih):
            if il >= ih: return None
            root = TreeNode(val = postorder[ph-1])
            # search in indorder
            for idx in range(il, ih):
                if inorder[idx] == root.val: break
            mid = ph-1-(ih-1-idx)
            root.right = rebuild(mid, ph-1, idx+1, ih)
            root.left = rebuild(pl, mid, il, idx)
            return root
        return rebuild(0, len(inorder), 0, len(inorder))

Version 2

利用map来保证 O ( N ) O(N) O(N)的时间效率。你永远都可以考虑优先用空间换时间。

# version 2
# 空间换时间
# Runtime: 52 ms, faster than 66.41% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 19.4 MB, less than 53.98% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        num2idx = dict(zip(inorder, range(len(inorder))))
        def rebuild(pl, ph, il, ih):
            if il >= ih: return None
            root = TreeNode(val = postorder[ph-1])
            # search in indorder
            idx = num2idx[root.val]
            mid = ph-1-(ih-1-idx)
            root.right = rebuild(mid, ph-1, idx+1, ih)
            root.left = rebuild(pl, mid, il, idx)
            return root
        return rebuild(0, len(inorder), 0, len(inorder))

Version 3

一种严格的 O ( N ) O(N) O(N)算法。非常优雅的实现。

理解该算法并不容易,要点就是要记住递归函数的定义,然后相信它。它不再是单独重构某一边的子树,而是定义为:给定stop,高速你在inoreder找到什么停止,函数负责把我的整颗树从数组里重构出来。

所有节点pop一次,所以复杂度 O ( N ) O(N) O(N).

话又说回来,递归算法,定义是最重要的,思考的关键是相信定义。可是,这比较适合用来理解递归代码。设计还是很难,这道题的实现非常优雅。

# version  3 O(n)的算法
# Runtime: 32 ms, faster than 99.61% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 17.7 MB, less than 94.95% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        # 告知stop,从二个数组重构整颗树,初始的stop为None
        def rebuild(stop):
            if  inorder[-1] != stop:
                # 新建当前节点
                root  = TreeNode(val=postorder.pop())
                # 重构右子树,inoreder的stop为当前节点的值,找到则右子树重构完毕
                root.right = rebuild(root.val)
                # 注意右子树重构完毕,意味着inorder中已经不存在左子树的节点了
                # 那么现在该干什么?当然是pop掉当前的节点了
                inorder.pop()
                # 重构左子树,左子树的stop值为本函数的stop相同,初始为None
                root.left = rebuild(stop)
                return root
        # 哨兵节点
        inorder = [None] + inorder
        return rebuild(None)

附,前序+中序的重构

# version 1
# 递归构造
# Runtime: 356 ms, faster than 10.68% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 18.7 MB, less than 60.97% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        def rebuild(pl, ph, il, ih):
            if il >= ih: return None
            root = TreeNode(val = postorder[ph-1])
            # search in indorder
            for idx in range(il, ih):
                if inorder[idx] == root.val: break
            mid = ph-1-(ih-1-idx)
            root.right = rebuild(mid, ph-1, idx+1, ih)
            root.left = rebuild(pl, mid, il, idx)
            return root
        return rebuild(0, len(inorder), 0, len(inorder))
    
# version 2
# 空间换时间
# Runtime: 52 ms, faster than 66.41% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 19.4 MB, less than 53.98% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        num2idx = dict(zip(inorder, range(len(inorder))))
        def rebuild(pl, ph, il, ih):
            if il >= ih: return None
            root = TreeNode(val = postorder[ph-1])
            # search in indorder
            idx = num2idx[root.val]
            mid = ph-1-(ih-1-idx)
            root.right = rebuild(mid, ph-1, idx+1, ih)
            root.left = rebuild(pl, mid, il, idx)
            return root
        return rebuild(0, len(inorder), 0, len(inorder))


# version  3 O(n)的算法
# Runtime: 32 ms, faster than 99.61% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
# Memory Usage: 17.7 MB, less than 94.95% of Python online submissions for Construct Binary Tree from Inorder and Postorder Traversal.
class Solution(object):
    def buildTree(self, inorder, postorder):
        # 告知stop,从二个数组重构整颗树,初始的stop为None
        def rebuild(stop):
            if  inorder[-1] != stop:
                # 新建当前节点
                root  = TreeNode(val=postorder.pop())
                # 重构右子树,inoreder的stop为当前节点的值,找到则右子树重构完毕
                root.right = rebuild(root.val)
                # 注意右子树重构完毕,意味着inorder中已经不存在左子树的节点了
                # 那么现在该干什么?当然是pop掉当前的节点了
                inorder.pop()
                # 重构左子树,左子树的stop值为本函数的stop相同,初始为None
                root.left = rebuild(stop)
                return root
        # 哨兵节点
        inorder = [None] + inorder
        return rebuild(None)

Ref

  • https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/discuss/34543/Simple-O(n)-without-map

你可能感兴趣的:(算法编程)