python_ACM模式《剑指offer刷题》二叉树3

题目:

python_ACM模式《剑指offer刷题》二叉树3_第1张图片

面试tips:

        若面试官无特殊要求直接优先采用思路一递归法(易想);若有特殊要求,例如不想要重复遍历中序序列来寻找根节点,则采取思路二,即将中序遍历存入到哈希表中,实现在中序遍历中取根节点的index是O(1)时间复杂度来取;若要求不采用递归的方式,则采取思路三巧妙的迭代法。

思路:

思路一:直接递归思想

        首先明白前序遍历是中左右,中序遍历是左中右。因此根据前序遍历序列,我们可以将序列中第一个值作为根节点,后遍历中序遍历找到根节点的位置index,则对于中序遍历的index左边A序列就是该根节点的左子树的中序遍历,对于中序遍历的index右边B序列就是该根节点的右子树的中序遍历。根据A序列的长度可以找到对应的在前序遍历中的A前序列,前序遍历中剩余的就是B前序列。根据A前和A又构成了新的前序遍历序列和中序遍历序列作为求左子树,右子树同理。

思路二:

        事先存储好中序遍历节点与index的映射关系入哈希表my_dict中。

        但是其思路与思路一就不同了。思路一在构造左右子树时是变更了前序遍历序列和中序遍历序列的,即左子树(是用左子树的前序遍历序列和左子树的中序遍历序列;右子树同理,依次递归下去)。但是思路二因为事先存储好的是整棵树的中序遍历序列,因此在构造其左右子树时就不能再用左右子树的前中遍历序列了,而是也用整棵树的前中遍历序列,因此这就需要在整棵树的前中遍历序列中找到左右子树所对应的index。

        因为哈希表my_dict存储了中序遍历节点与index的映射关系,也就是通过my_dict,可以找到某棵树(子树)的根节点,也就是找到左右子树在中序遍历中的分界线。因此要确定一个树的左右子树,只需要前序遍历中(这里为什么是前序遍历的,因为只有通过前序遍历才能得到root)root的index,中序遍历的左右边界left的index和right的index(这里因为只要root,就可以通过中序遍历得到在中序中root的index,这样再结合left和right的index,就顺利在原始的中序遍历序列上寻找到对应的左右子树的中序遍历序列的所对应的下标区间了!)这样再结合index所求到左右子树的长度,进而在前序遍历序列中找到左右子树root的index,递归下去。详细看代码。读完这段话再看代码就懂了。

思路三:迭代法

        思路三讲解起来有点复杂。这里讲解的很清晰。. - 力扣(LeetCode)

代码实现:

思路一:

时复最理想的情况下(平衡二叉树)是O(n),空复为O(logn);最不理想的情况下(树退化成只有左孩子的单链表)是O(n^2),空复为O(n)。显然时复主要花费时间在在中序遍历中寻找根节点位置,因此有思路二。

class TreeNode:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right
def inorder_Traversal(root):
    # 中序遍历验证结果
    if not root:
        return None
    inorder_Traversal(root.left)
    print(root.val, end= ' ')
    inorder_Traversal(root.right)

class Solution:
    def buildTree(self, preorder, inorder):
        # 前序找出根节点 中序找出左右子树
        # 采用递归 每次根据【前序找出根节点 中序找出左右子树】来划分出前序和中序序列
        # 终止条件
        if not preorder:
            return None
        # 单层递归逻辑
        # --------由前序得到根节点--------
        root = TreeNode(preorder[0])
        # -----由中序+根节点 得到左子树和右子树中序序列 以及 左子树个数(其作用是划分前序序列的左右子树)
        for i in range(len(inorder)):
            if inorder[i] == root.val:
                break
            # 得到此时的i即为根节点在中序的位置
        left_inorder = inorder[:i]
        right_inorder = inorder[i+1:]
        # -------利用左子树个数划分前序序列的左右子树
        m = len(left_inorder)
        left_preorder = preorder[1:m+1]
        right_preorder = preorder[m+1:]
        # ---------递归构造
        root.left = self.buildTree(left_preorder, left_inorder)
        root.right = self.buildTree(right_preorder, right_inorder)
        return root

if __name__ == '__main__':
    preorder = [3,9,20,15,7]
    inorder = [9,3,15,20,7]
    a = Solution()
    root = a.buildTree(preorder, inorder)
    inorder_Traversal(root)

思路二:

时复O(n),空复O(n)(链表空间O(n)+哈希表空间O(n)+递归空间O(h)).

class TreeNode:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right
def inorder_Traversal(root):
    # 中序遍历验证结果
    if not root:
        return None
    inorder_Traversal(root.left)
    print(root.val, end= ' ')
    inorder_Traversal(root.right)

class Solution:
    def buildTree(self, preorder, inorder):
        def dfs(root, left, right):
            # 根节点下标由前序给出,左右子树的左右边界需要由中序给出
            # 终止条件
            if left > right:
                return None
            # 单层递归
            node = TreeNode(preorder[root])
            i = my_dict[preorder[root]]
            node.left = dfs(root+1, left, i - 1)
            # root + i - left + 1 == root + i - 1 - left + 1 + 1,其中i - 1 - left + 1为左子树的长度
            node.right = dfs(root + i - left + 1, i + 1, right)
            return node
        my_dict = {}
        for i in range(len(inorder)):
            my_dict[inorder[i]] = i
        return dfs(0, 0, len(preorder)-1)

if __name__ == '__main__':
    preorder = [3,9,20,15,7]
    inorder = [9,3,15,20,7]
    a = Solution()
    root = a.buildTree(preorder, inorder)
    inorder_Traversal(root)

思路三:

时复为O(n),空复为O(n)(链表空间O(n)+stack空间O(h)).

class TreeNode:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right
def inorder_Traversal(root):
    # 中序遍历验证结果
    if not root:
        return None
    inorder_Traversal(root.left)
    print(root.val, end= ' ')
    inorder_Traversal(root.right)

class Solution:
    def buildTree(self, preorder, inorder):
        # 利用迭代法
        # 对前序数组从左往右,第一个元素为根节点,
        # 判断接下来的素每个元是左子树(前序中若存在左子树则一定是前一个节点的左孩子)还是右子树(前序中右子树不一定为其的右孩子,需要倒序匹配判断)
        if not preorder:
            return None
        root = TreeNode(preorder[0])
        stack = [root]
        index = 0
        for i in range(1, len(preorder)):
            node = stack[-1]
            next_node = TreeNode(preorder[i])
            if node.val != inorder[index]:
                # 如果与中序不相等,则说明i所对应的是左子树(即栈顶节点的左孩子)
                node.left = next_node
                # 遍历过还得加入栈中
                stack.append(next_node)
            else:
                # 如果与中序相等,则说明i所在的是右子树,那么到底是谁的右孩子呢?此时就需要进一步判断
                # 需要将stack从相等的开始倒序遍历找到最后一个相等的节点,那么i所在的位置就是这个最后一个相等的节点的右孩子
                while stack and stack[-1].val == inorder[index]:
                    # 相等则两个都进行移动
                    node = stack.pop()
                    index += 1
                # 跳出来则说明stack为空或者不相等了--如果是对应的前中序数组则应该不会出现空的情况
                node.right = next_node
                stack.append(node.right)
        return root

if __name__ == '__main__':
    preorder = [3,9,20,15,7]
    inorder = [9,3,15,20,7]
    a = Solution()
    root = a.buildTree(preorder, inorder)
    inorder_Traversal(root)

你可能感兴趣的:(剑指offer练习,算法)