[LeetCode] #105. Construct Binary Tree from Preorder and Inorder Traversal

題目

Given two integer arrays preorder and inorder where preorder is the preorder traversal of a binary tree and inorder is the inorder traversal of the same tree, construct and return the binary tree.

直覺解

構想:

有稍微偷看一下 Solution 區的分析。

交叉對照 preorderinorder,以找出 root、left subtree 和 right subtree。

  • preorder[0] 是 root

  • inorder 中,root 的左邊是 left subtree,右邊是 right subtree

python 3 實作(失敗):

# class TreeNode: 
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder:
            return None
        if len(preorder) == 1:
            return TreeNode(preorder[0])

        root = TreeNode(preorder[0])
        inorder_left = inorder[0:inorder.index(root.val)]
        inorder_right = inorder[inorder.index(root.val)+1:]
        preorder_left = preorder[1:preorder.index(inorder_left[-1])+1]
        preorder_right = preorder[preorder.index(inorder_left[-1])+1:]
        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)

        return root

Submission Detail

Runtime Error Message: IndexError: list index out of range

Last executed input:
[1,2]
[1,2]

失敗原因

inorder_left[],所以 inorder_left[-1] 會報錯

構想:

inorder_left 不是 [] 的話,才使用 inorder_left[-1] 語法

python 3 實作(失敗):

class Solution: 
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder:
            return None
        if len(preorder) == 1:
            return TreeNode(preorder[0])

        root = TreeNode(preorder[0])
        inorder_left = inorder[0:inorder.index(root.val)]
        inorder_right = inorder[inorder.index(root.val)+1:]

        preorder_left = []
        preorder_right = []
        if len(inorder_left) > 0:
            preorder_left = preorder[1:preorder.index(inorder_left[-1])+1]
            preorder_right = preorder[preorder.index(inorder_left[-1])+1:]
        else:
            preorder_right = preorder[preorder.index(root.val)+1:]

        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)

        return root

Submission Detail

Wrong Answer

Input:
[1,2,3]
[3,2,1]

Output: [1,2,3]

Expected: [1,2,null,3]

失敗原因

切 left subtree 時切錯了。inorder_left 是正確的 [3, 2],但是 preorder_left 是 [2]。

構想:

切 preorder_subtree 時,改成根據 inorder_subtree 的長度去切,不要根據inorder_subtree[-1] 的位置去切。

python 3 實作:

class Solution: 
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder:
            return None
        if len(preorder) == 1:
            return TreeNode(preorder[0])

        root = TreeNode(preorder[0])
        inorder_left = inorder[0:inorder.index(root.val)]
        inorder_right = inorder[inorder.index(root.val)+1:]
        preorder_left = preorder[1:len(inorder_left)+1]
        preorder_right = preorder[len(inorder_left)+1:]

        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)

        return root

效能:

Runtime: 224 ms, faster than 17.33% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.
Memory Usage: 88.4 MB, less than 19.53% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.

研究別人的解

來源:sample 136 ms submission

研究重點:如何改善效能。

別人的 python 3 實作:

class Solution: 
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if inorder:
            root = preorder.pop(0)
            index = inorder.index(root)

            root = TreeNode(root)

            root.left = self.buildTree(preorder, inorder[:index])
            root.right = self.buildTree(preorder, inorder[index + 1:])
            return root

效能:

Runtime: 136 ms, faster than 52.96% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.
Memory Usage: 53 MB, less than 49.93% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.

研究感想

preorder 的用途是找出 root。靠 preorder.pop(0) 和 python 傳遞 preorder 的方式,那麼每次呼叫 buildTree 時,preorder 裡面自然只會剩下跟 inorder 一樣多的元素。

這樣就不需要再計算 preorder_left 和 preorder_right 分別是多少了,節省計算時間。這個傳遞 preorder 的方式,不會複製新的 list,節省了使用的記憶體。

研究別人的解

來源:sample 56 ms submission

研究重點:如何改善效能。

別人的 python 3 實作:

class Solution: 
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:

        def array_to_tree(left, right):

            nonlocal p

            # if there are no elements to construct the tree
            if left > right: return None


            # the current element in preorder is the root of this subtree
            root = TreeNode(preorder[p])
            p += 1

            # build left and right subtrees under this root
            # exclude inorder_v2i[root.val] element because it's the root
            root.left = array_to_tree(left, inorder_v2i[root.val] - 1)
            root.right = array_to_tree(inorder_v2i[root.val] + 1, right)


            return root


        p = 0 # preorder list index

        # map values to indices in the inorder list so can look up index by value
        inorder_v2i = {v: i for (i, v) in enumerate(inorder)} # value to index -- a dict comprehension!

        # https://stackoverflow.com/questions/1747817/create-a-dictionary-with-list-comprehension

        return array_to_tree(0, len(preorder) - 1)

效能:

Runtime: 48 ms, faster than 98.37% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.
Memory Usage: 19 MB, less than 70.25% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.

研究感想

把 inorder 轉成 dictionary,加快用 value 查詢 index 的速度。

不複製 inorder 也不複製 preorder,節省記憶體空間。array_to_tree 接受的參數是 inorder 的 index。不刪除 preorder 中的元素,只依靠 p 的變動紀錄 root 的 index。

研究別人的解

來源:sample 36 ms submission

研究重點:不用遞迴的寫法。

別人的 python 3 實作:

class Solution: 
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        T = [TreeNode(12000003)]
        p = 0
        i = 0
        LEFT, RIGHT = 1, 2
        direction = LEFT
        D = {}
        while p < len(preorder) and i < len(inorder):
            pre = preorder[p]
            inn = inorder[i]

            # exist in T
            if inn in D:
                #T = T[:D[inn]+1]
                del T[D[inn]+1:]
                direction = RIGHT
                i += 1
            elif inn == pre:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = RIGHT
                p+=1
                i+=1
            else:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = LEFT
                p+=1

        #print(T)
        return T[1]

效能:

Runtime: 52 ms, faster than 94.45% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.
Memory Usage: 17.7 MB, less than 99.82% of Python3 online submissions for Construct Binary Tree from Preorder and Inorder Traversal.

研究感想

T 用來存創造出的所有 TreeNode,T[0] 是 dummy root。p 和 i 分別紀錄 preorder 和 inorder 目前迭代到哪個 index。D 用來快速查詢「val 是某某值的 TreeNode 在 T 的哪個 index」。direction 代表這個節點要接在 T[-1] 這個節點的左邊或右邊。

preorder = [1, 2, 3], inorder = [3, 2, 1] 帶入來看的話,因為這組 input 只有長左邊的節點,所以 while 迴圈中有執行到的部分只有:

        while p < len(preorder) and i < len(inorder): 
            pre = preorder[p]
            inn = inorder[i]

            # exist in T
            if inn in D:
                #T = T[:D[inn]+1]
                del T[D[inn]+1:]
                direction = RIGHT
                i += 1
            elif inn == pre:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = RIGHT
                p+=1
                i+=1
            else:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = LEFT
                p+=1

preorder = [1, 2], inorder = [1, 2] 帶入來看的話,因為這組 input 只有長右邊的節點,所以 while 迴圈中有執行到的部分只有:

        while p < len(preorder) and i < len(inorder): 
            pre = preorder[p]
            inn = inorder[i]

            # exist in T
            if inn in D:
                #T = T[:D[inn]+1]
                del T[D[inn]+1:]
                direction = RIGHT
                i += 1
            elif inn == pre:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = RIGHT
                p+=1
                i+=1
            else:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = LEFT
                p+=1

preorder = [1, 2, 3], inorder = [2, 1, 3] 帶入來看的話,因為這組 input 只有長右邊的節點,所以 while 迴圈中有執行到的部分只有:

        while p < len(preorder) and i < len(inorder): 
            pre = preorder[p]
            inn = inorder[i]

            # exist in T
            if inn in D:
                #T = T[:D[inn]+1]
                del T[D[inn]+1:]
                direction = RIGHT
                i += 1
            elif inn == pre:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = RIGHT
                p+=1
                i+=1
            else:
                node = TreeNode(pre)
                if direction == LEFT:
                    T[-1].left = node
                else:
                    T[-1].right = node
                T.append(node)
                D[pre] = len(T) - 1
                direction = LEFT
                p+=1

if inn in D 那一段的作用:因為每次都是幫 T[-1] 增加子節點,加完之後就把新的節點 append 到 T 裡,所以,如果某個節點同時有兩個子節點 A, B,而 A 已經先掛到父節點底下的話,就需要把 T 裡面的 A 節點刪掉,才能用 T[-1] 的方式選到 A 的父節點,再把 B 節點掛上去。

Comments

Popular posts from this blog

資料關聯

程式設計相關社群、活動

TCP/ IP 通訊協定