LeetCode #105 Construct Binary Tree from Preorder and Inorder Traversal - 刷題之旅
1 題目描述
提供給你 preorder 與 inorder 的數列,請你建立一個二元樹。首先 讓我們先了解 preorder 與 inorder 的定義。
Inorder Traversal -> LEFT -> ROOT ->RIGHT
PreOrder Traversal -> ROOT -> LEFT -> RIGHT
2 解法
你會發現,preorder 第一筆會是 Root,後面會先遍歷left sub-tree整顆跑完,才會再遍歷right sub-tree。而 inorder 是先遍歷left sub-tree,再遍歷root,最後是right sub-tree。你會發現 preorder 跟 inorder 的優缺點如下:
- preorder:
- 好處:因為第一個值是root,所以可以直接取得root的值
- 缺點:無法知道左右子樹的範圍
- inorder:
- 好處:可以知道左右子樹的範圍
- 缺點:無法知道root的值
因此我們要利用彼此雙方的好處,互補彼此的缺點。這時候我們就可以利用遞迴的方式來建立二元樹。
2.1 取subtree的範圍
Step1: 在preorder找到root + 在 inorder找到左右子樹長度
- 那就是如果我們先取得preorder的第一個值,就可以知道root的值是什麼
- 然後再根據這個值去inorder中找到root的位置,這樣就可以知道左右子樹的範圍。
- 左子樹起點=0 ~ 左子樹 root_index (不包含) 簡單來說就是
inorder[0:root_index]
- 右子樹起點=root_index+1 ~ 右子樹結束點=len(inorder) (包含) 簡單來說就是
inorder[root_index+1:]
- 左子樹起點=0 ~ 左子樹 root_index (不包含) 簡單來說就是
大概是這種感覺:
1 | def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
Step2: 根據左右子樹的長度,在preorder切割左右子樹
因為我們用inorder知道了左右子樹的,我們就可以在 preorder 裡面從 preorder[0+1:]
(root的下一個node) 開始切割左右子樹
- 使用
i_left
的長度,在[1:]
切割左子樹 - [0+1:] 這個區間是由 左子樹 + 右子樹 所組成
- 左子樹起點=1 ~ 左子樹結束點=1+left_len (不包含) 簡單來說就是
preorder[1:1+left_len]
- 右子樹起點=1+left_len ~ 右子樹結束點=len(preorder) (包含) 簡單來說就是
preorder[1+left_len:]
- 左子樹起點=1 ~ 左子樹結束點=1+left_len (不包含) 簡單來說就是
- 從 preorder 得到的左右子樹,取第一個值,又可以得到 root
- inorder
- 把preorder從[1:] 切成兩半左右子樹後,我們又可以開始遞迴,找到這兩個子樹的 root
大概是這種感覺:
1 | def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
Step3: 當左右子樹為空時,回傳None
1 | def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
精簡
我們稍微整理一下,這個遞迴的過程
- left_len 其實可以用 root_index 來取代,因為左子樹的長度就是 root_index
- 最後 就可以精簡成這樣
1 | def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
總結一下順序:
- inorder切出左右子樹: 在 inorder 用 root 切出左右子樹的範圍
- inorder找出左右子樹長度:拿 inorder 左右子樹的長度,在 preorder 切出左右子樹的範圍
- preorder 切出左右子樹,以找出root: preorder 的左右子樹一出來,就可以知道左右子樹的 root 是什麼(因為preorder的第一個值是root)
- 大小問題關係式: 然後把 root.left 與 root.right 填入
這題的大問題與小問題的關係是
- 大問題:建立一個二元樹
- 小問題:建立左右子樹
- 大小問題的關聯式:假設知道 root.left 與 root.right 最後要回傳 root
- 左子樹的範圍是
preorder[1:root_index+1]
與inorder[0:root_index]
,右子樹的範圍是preorder[root_index+1:]
與inorder[root_index+1:]
- 我們要回傳 root,並且把左右子樹填入
- root 一定是 preorder 的第一個 node,因此 root.left 跟 root.right 就是左右子樹中 preorder 的第一個 node
- 左子樹的範圍是
2.2 queue 的解法
這題解法也很酷,你可以從上圖看到,preorder 儘管被切割成 left 與 right,但是我們真正會使用到 preorder 的地方就是 root = TreeNode(preorder[0])
我們只是想拿 preorder 的第一個值,如果我們每次取完root後,就 pop 掉,下一個數值就會是肯定是子樹的root。因此我們也不用大費周章的去切割preorder,只要每次取完root後,就 pop 掉,下一個數值就會是肯定也會是某個子樹的root。
1 | def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
3 總結
這題花了一個小時,概念是想出來了,但是還是寫不太出來Recursive…,後來看了一下解答,慢慢體會想法被實踐的感覺…呵呵。希望Recursion會慢慢進步。