剑指 Offer 07. 重建二叉树

https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
二叉树的前序遍历和中序遍历重构二叉树 (这道题的url很有意思)
前序遍历:根左右
中序遍历:左根右
注意前序遍历中序遍历,中序遍历后序遍历都可以重建二叉树,前序遍历和后序遍历不能重建出唯一二叉树,因为没有中序遍历,定位不了根节点的左子树和右子树。

  1. 递归法
    递归法思路:前序遍历的第一个节点是根节点,找到根节点在中序遍历数组中的位置,数组左半部分为根节点的左子树,右半部分为根节点的右子树。同时反推前序遍历数组,前序遍历中1:左子树长度为当前根节点左子树节点,左子树长度+1:数组末尾为当前右子树,则又变成了左子树和右子树根据前序遍历和中序遍历进行重建的过程。
    递归终止条件:传递数组为空,返回null空节点
    递归主体:前序遍历数组第一个value为根节点value,new 根节点,并return此节点
    递归体:找到前序遍历第一个value在中序遍历中位置,记录位置为index,则前序遍历数组[1:index+1],中序遍历数组[0:index]为当前根节点左子树,前序遍历数组[index+1,end],中序遍历数组[index+1:end]为当前根节点右子树。递归完成当前根节点左右子树的建立。
    时间复杂度O(n),空间复杂度O(n)
    但是本身递归调用有函数多层出栈入栈过程,比较耗时也比较耗内存。
    # Definition for a binary tree node.
     class TreeNode(object):
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    class Solution(object):
        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])
            for i in range(0, len(inorder)):
                if inorder[i] == preorder[0]:
                    index = i
                    break
            if (index+1) <= len(preorder):
                root.left = self.buildTree(preorder[1:index+1], inorder[0:index])
            if (index+2) <= len(preorder):
                root.right = self.buildTree(preorder[index+1:], inorder[index+1:])
            return root
    
    在这里插入图片描述
  2. 迭代法 参考乐扣官方题解 https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/

    preorder = [3,9,8,5,4,10,20,15,7]
    inorder = [4,5,8,10,9,3,15,20,7]
    首先,看前序遍历数组,前序遍历相邻两个元素,譬如【3,9】有3种可能关系
    a. 9是3的左节点
    b. 9是3的右节点
    c. 3是叶子节点,则9是3的某个祖先节点的右节点
    则先开始遍历前序遍历队列,第一个元素3,为整棵树根节点,没有疑问。
    第二个元素9,需要判断其与3的关系是abc 3种情况中的哪一个。看中序遍历数组进行辅助,中序遍历数组的第一个元素不是3,则3一定是有左子树的,因为前序遍历顺序是根左右,如果9为其右节点,而3又有左子树,则9一定不能在前序遍历数组中紧挨着3,则9为3的左节点。
    接着看元素8,同理,9也不是中序遍历的第一个元素,因此8也是9的左节点,以此类推,5为8的左节点,4为5的左节点。
    现在前序遍历数组遍历到4,4是中序遍历数组第一个元素,因此判断4没有左子树。则10与4的关系为或者是4的右节点,或者是4的祖先节点的右节点。从前面的判断已经可以得到,4的祖先节点有3,9,8,5,因此当前需要做的是判断10到底为哪个节点的右节点。考虑若是所有节点都没有右节点,则前序遍历为根左,中序遍历为左根,则前序遍历翻转后应与中序遍历完全相同,但当当前节点有右节点,前序遍历翻转为右左根,中序遍历为左根右,对应不上。因此,将已循环到的前序遍历翻转和中序遍历进行比较,最后一个相同的元素即为10的根元素。
    前序遍历翻转后结果为4,5,8,9,3,中序遍历为4,5,8,10,9,3,最后一个相同元素为8,则10为8的右子节点。
    此过程用代码实现为遍历前序数组,3,9,8,5依次入栈,中序遍历指针midindex此时一直停留在0号位置。当判断前序遍历preindex元素=中序遍历midindex元素,前序遍历出栈,中序遍历midindex++,直到midindex元素和栈顶元素不相等,则当前前序遍历遍历到的元素为刚刚出栈元素的右子节点。此时,前序遍历栈中元素为3,9,中序遍历指针为midindex=3,指向元素10 。
    前序遍历将10入栈,判断下一节点20与10的关系,当前前序遍历遍历元素为10,midindex指向也为10,则10的下一元素20应该是10或者其祖先的右子节点。同样,进行前序遍历出栈和midnindex++元素判断,找到第一个不相等元素。当前栈中元素为3,9,没有不相等元素,则出栈最后一个元素3为20的根节点。
    前序遍历将20入栈,判断15与20的关系。20与当前midindex指向15不相等,则15为20的左节点,当前栈中元素为20。
    前序遍历将15入栈,15与中序遍历当前指向15相同,则7为15或其祖先节点的右子节点,当前栈中元素为20,15,则判断7为20的右子节点。
    整棵树重建完成,其实主体就是两个判断。当当前前序遍历中元素和中序遍历元素不相等,则元素下一元素为当前元素的左子节点,若相同,则为某一已遍历节点的右节点,某一节点的找寻方式为翻转已遍历前序遍历队列,找到和中序遍历队列最后一个相等元素。
    class Solution(object):
      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])
          pre_stack = list()
          pre_stack.append(root)
          mid_index = 0
          for pre_index in range(1, len(preorder)):
              node = pre_stack[-1]
              if node.val != inorder[mid_index]:
                  node.left = TreeNode(preorder[pre_index])
                  pre_stack.append(node.left)
              else:
                  while len(pre_stack) != 0 and pre_stack[-1].val == inorder[mid_index]:
                      node = pre_stack.pop()
                      mid_index = mid_index + 1
                  node.right = TreeNode(preorder[pre_index])
                  pre_stack.append(node.right)
          return root
    
    思路上比递归绕很多,希望下次刷题的时候自己还能记得。但耗时和内存都降低的非常多
    几秒前 通过 20 ms 14.2 MB Python
    35 分钟前 通过 32 ms 14.4 MB Python
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值