1. 程式人生 > 實用技巧 >劍指 Offer 33. 二叉搜尋樹的後序遍歷序列(單調棧or遞迴)

劍指 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

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/mian-shi-ti-33-er-cha-sou-suo-shu-de-hou-xu-bian-6/

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/dan-diao-di-zeng-zhan-by-shi-huo-de-xia-tian/