劍指 Offer 33. 二叉搜尋樹的後序遍歷序列(單調棧or遞迴)
- 題目描述
首先這裡需要知道二叉搜尋樹的性質的先驗知識:
二叉搜尋樹又叫二叉排序樹,它或者是一顆空樹,或者是具有以下性質的二叉樹:
- 若它的左子樹不為空,則左子樹上的所有結點都小於根節點上的值
- 若它的右子樹不為空,則右子樹上的所有結點都大於根節點上的值
- 它的左右子樹也分別是二叉搜尋樹
那麼二叉搜尋數的後續遍歷一定有以下特性:
- 所有的左子樹均小於根節點
- 所有右子樹均大於根節點
- 最後一個一定是根節點
輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷結果。如果是則返回 true,否則返回 false。假設輸入的陣列的任意兩個數字都互不相同。 參考以下這顆二叉搜尋樹: 5 / \ 2 6 / \ 1 3 示例 1: 輸入: [1,6,3,2,5] 輸出: false 示例 2: 輸入: [1,3,2,6,5] 輸出: true 提示: 陣列長度<= 1000
- 題解一:遞迴解法參照劍指offer
思路1:自己的解法
首先需要判斷左子樹節點值小於根節點的值,然後判斷右子樹節點值大於根節點的值,然後用遞迴判斷左子樹是不是二叉搜尋樹,再遞迴判斷右子樹是否是二叉搜尋樹,最後將左右子樹的判斷結果bool值相與即可。
class Solution: ''' 參考劍指offer書的解法,自己的實現 ''' def verifyPostorder(self, postorder): if not postorder: return True root = postorder[len(postorder)-1] left= True for i in range(len(postorder)): if root < postorder[i]: break left_tree = postorder[:i] for j in range(i,len(postorder)-1): if postorder[j] < root: return False if len(left_tree) > 1: #判斷左子樹 left= self.verifyPostorder(left_tree) elif len(left_tree) == 1: if left_tree[0] > root: return False right = True right_tree = postorder[i:len(postorder)-1] if len(right_tree) > 1: #判斷右子樹 right = self.verifyPostorder(right_tree) return(left & right)
思路二:遞迴+分治
跟上面思路一差不多,都是判斷左子樹所有節點均小於根節點,右子樹左右節點均大於根節點,然後返回了3個bool值,當這三個均為True,這棵樹才是二叉搜尋樹
def verifyPostorder(self, postorder: [int]) -> bool: def recur(i, j): #i j為左右邊界 if i >= j: return True #此時根節點數量小於等於1,無需判別 p = i #如果左子樹符合的話,p則指向左子樹的和右子樹的分界點 while postorder[p] < postorder[j]: p += 1 m = p #此時判斷p == j如果右子樹都比根節點大的話,此時p則會指向根節點的位置 while postorder[p] > postorder[j]: p += 1 #左子樹區間(i ,m-1),右子樹區間(m, j-1) return p == j and recur(i, m - 1) and recur(m, j - 1) #遞迴 return recur(0, len(postorder) - 1)
- 題解二:單調遞增棧,逆向遍歷陣列
由於二叉搜尋樹是left <root < right的,後續遍歷則是left-right-root,為了保證單調性,可以將後續遍歷倒過來,則變成root->right->left。
翻轉先序遍歷是root->right->left的,基於這樣的性質和遍歷方式,我們知道越往右越大,這樣,就可以構造一個單調遞增的棧,來記錄遍歷的元素。
用單調棧的原因是:
往右子樹遍歷的過程,value是越來越大的,一旦出現了value小於棧頂元素value的時候,就表示要開始進入左子樹了
(如果不是,就應該繼續進入右子樹,否則不滿足二叉搜尋樹的定義),但是這個左子樹是從哪個節點開始的呢?
單調棧幫我們記錄了這些節點,只要棧頂元素還比當前節點大,就表示還是右子樹,要移除。
因為我們要找到這個左孩子節點直接連線的父節點,也就是找到這個子樹的根,只要棧頂元素還大於當前節點,就要一直彈出,直到棧頂元素小於節點,或者棧為空。棧頂的上一個元素就是子樹節點的根。
接下來,陣列繼續往前遍歷,之後的左子樹的每個節點,都要比子樹的根要小,才能滿足二叉搜尋樹,否則就不是二叉搜尋樹。
def verifyPostorder(self, postorder: [int]) -> bool: stack, root = [], float("+inf") for i in range(len(postorder) - 1, -1, -1): if postorder[i] > root: return False #左子樹比root節點大,則不是二叉搜尋樹 while (stack and postorder[i] < stack[-1]): root = stack.pop() #root記錄子樹節點的根 stack.append(postorder[i]) return True
- 單調棧
單調棧,顧名思義就是棧內元素單調按照遞增(遞減)順序排列的棧。
- 單調遞增棧:棧中資料出棧的序列為單調遞增序列
- 單調遞減棧:棧中資料出棧的序列為單調遞減序列
現在有一組數10,3,7,4,12。從左到右依次入棧,則如果棧為空或入棧元素值小於棧頂元素值,則入棧;否則,如果入棧則會破壞棧的單調性,則需要把比入棧元素小的元素全部出棧。單調遞減的棧反之。
-
10入棧時,棧為空,直接入棧,棧內元素為10。
-
3入棧時,棧頂元素10比3大,則入棧,棧內元素為10,3。
-
7入棧時,棧頂元素3比7小,則棧頂元素出棧,此時棧頂元素為10,比7大,則7入棧,棧內元素為10,7。
-
4入棧時,棧頂元素7比4大,則入棧,棧內元素為10,7,4。
-
12入棧時,棧頂元素4比12小,4出棧,此時棧頂元素為7,仍比12小,棧頂元素7繼續出棧,此時棧頂元素為10,仍比12小,10出棧,此時棧為空,12入棧,棧內元素為12。
參考連結:https://blog.csdn.net/lucky52529/article/details/89155694