LeetCode 105/106 Medium 由先序/後序with中序構造二叉樹 Python
二叉樹遍歷的幾種方式
DFS,深度優先遍歷:
後序,先序,中序
BFS寬度優先遍歷:
按層遍歷
105
演算法:遞迴
思路:
其實和本科上課時學到的人工根據先序遍歷和中序遍歷構造二叉樹的思路是一樣的。
(一定不能沒有中序遍歷的序列否則無法判斷左右子樹)
在先序序列中找到根節點,然後去中序遍歷序列中找到根節點的位置將中序遍歷序列以root
為界分成左右兩個子樹,然後遞迴,從左子樹開始繼續從先序遍歷的列表中依次取出根節點,
將傳進來的左子樹的中序遍歷列表按先序的跟劃分為其左右子樹,若其左右子樹為空,則返回None,
何為左右子樹為空?即以它為分割,在中序遍歷裡面該節點的左右都為0唄,或者一方為0,就是None嘛。
同理右子樹也根據此思路遞迴去構建即可,最後返回root
注意:
1.這裡要注意的是,如何確定向下一層遞迴的時候,傳給下一層的preorder和inorder是怎樣的,
對inorder來說很好講,傳左右側的切片,對preorder來說,其實要傳的就是它本身,或者說是一個全域性
的preorder,因為在左子樹遍歷的過程中,就應該把preorder中左子樹的節點都彈出了,這樣才能保障
在構建root的右子樹時,preorder的首部preorder[0]或者說佇列preorder的首部就是右子樹的根節點,
根據python的傳參特點,直接向下傳preorder就好了,不要傳切片
2. 向下遞迴的時候,一定要從左子樹開始構建!可以對比106題來更好地理解這一點,因為先序遍歷
時是"根-->左-->右",即根元素的右邊近鄰著的是左子樹,所以pop根的順序其實隱含了是從左側開始pop,
每次彈出的其實都是左側的節點,所以重建的時候,因為preorder是一個"全域性變數",所以先構建完左子樹,
再把構建完後已經產生變化,將左子樹節點都pop掉的preorder傳給右子樹進行右子樹的構建,此時就可以
保證右子樹中訪問的preorder的元素都是右子樹的元素了
複雜度分析:
時間:ON2,inorder.index尋找下標的操作要ON,巢狀遞迴ON,總ON2
空間:ON,儲存整個樹節點,且遞迴棧也有空間
def buildTree(self, preorder, inorder):
if inorder == []:
return None
root_val = preorder.pop(0)
root = TreeNode(root_val)
index = inorder.index(root_val)
root.left = self.buildTree(preorder, inorder[:index])
root.right = self.buildTree(preorder, inorder[index + 1:])
return root
106
演算法:遞迴
思路:
和105題用先序遍歷序列和中序遍歷序列解的方法相似,不同的是,
1. 後序遍歷,根節點是最後,所以直接pop()出棧
而105題用先序遍歷拿到的根節點,是pop(0)出佇列,
2.構建子樹的時候,從右子樹開始構建,因為後序遍歷是"左-->右-->根",當我從列表右側pop()根節點
出棧的時候,根節點近鄰的是左側的右子樹,所以從右子樹先開始構建,就可以保障用同全域性的postorder
向後遞迴傳遞,在右子樹的節點都構造完後繼續訪問同一個postorder就可以拿到左子樹的根節點了
複雜度分析:
時間:ON2,inorder.index尋找下標的操作要ON,巢狀遞迴ON,總ON2
空間:ON,儲存整個樹節點,且遞迴棧也有空間
def buildTree(self, inorder, postorder):
if inorder == []:
return None
root_val = postorder.pop()
index = inorder.index(root_val)
root = TreeNode(root_val)
root.right = self.buildTree(inorder[index + 1:], postorder)
root.left = self.buildTree(inorder[:index], postorder)
return root