[題解]劍指 Offer 33. 二叉搜尋樹的後序遍歷序列(C++)
題目
輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷結果。如果是則返回true,否則返回false。假設輸入的陣列的任意兩個數字都互不相同。
參考以下這顆二叉搜尋樹:
5
/ \
2 6
/ \
1 3
示例 1:
輸入: [1,6,3,2,5]
輸出: false
示例 2:
輸入: [1,3,2,6,5]
輸出: true
提示:
- 陣列長度 <= 1000
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof
著作權歸領釦網路所有。商業轉載請聯絡官方授權,非商業轉載請註明出處。
思路
二叉搜尋樹滿足左子樹元素小於根節點元素小於右子樹元素的條件,同時後序遍歷得到的節點順序是左子樹->右子樹->根節點。利用分治的思想,先確定根節點(陣列結尾處元素),然後確定左子樹和右子樹,如果發現左子樹中有節點的元素大於根節點元素或者右子樹中有節點的元素小於根節點元素,就返回false;沒有就繼續對左子樹和右子樹進行判斷,並將左子樹和右子樹的遞迴判斷結果作為判定依據返回。
時間複雜度\(O(n^2)\),空間複雜度O(n)。
程式碼
class Solution { public: bool verifyPostorder(vector<int>& postorder) { int n = postorder.size(); return verifyPostorder(postorder, 0, n - 1); } private: bool verifyPostorder(vector<int>& postorder, int l, int r) { if(l >= r) return true; int pos = l; while(postorder[pos] < postorder[r]) ++pos; int m = pos; while(postorder[pos] > postorder[r]) ++pos; return pos == r && verifyPostorder(postorder, l, m - 1) && verifyPostorder(postorder, m, r - 1); } };
改進
這題還有一種思路是根據單調棧來做,以題中的二叉樹為例,其後序遍歷為[1,3,2,6,5],若將其翻轉,即為[5,6,2,3,1]。翻轉後的陣列arr有兩個特點:
- 若arr[i] < arr[i + 1],則arr[i + 1]必定是arr[i]的右子節點。為什麼?從未翻轉的數組裡看就能知道,後序遍歷最後遍歷到根節點,那麼一個節點的右子節點必定出現在其左邊第一個位置,因為遍歷順序是:左子樹->右子樹的左子樹->右子樹的右子樹->右子樹的根(即根的右子節點)->根。那麼在翻轉後的陣列中,下一個元素比當前元素大,必定是其右子節點。
- 若arr[i] > arr[i + 1],則arr[i + 1]一定是下標從0到i中比arr[i + 1]大的元素中最小的那個。
基於上面的特點,我們用一個單調棧記錄遍歷到的元素,當陣列遞增時一直入棧,因為這時我們在向右子樹一直深入;一旦遇到陣列元素比棧頂元素小,就需要開始丟擲元素,注意我們這時在右子樹裡面,所以丟擲元素時記錄丟擲的棧頂元素,直到我們遇到棧頂元素比當前元素小,這時說明上一個丟擲的元素就是當前元素的父節點,而我們剛剛進入其左子樹!把上一個元素記下來作為臨時根節點,我們繼續向後遍歷陣列,一旦發現有元素比臨時根大(在二叉搜尋樹中這是不可能的,因為比臨時根節點大的都是其右子樹上的元素,而這些元素已經被我們拋掉了)就返回false;如果又遇到降序排列,就重複之前出棧的操作。遍歷完整個陣列之後返回true。
時間複雜度O(n),空間複雜度O(n)。
程式碼
class Solution {
public:
bool verifyPostorder(vector<int>& postorder) {
stack<int> stk;
int root = INT_MAX;
for(int i = postorder.size() - 1; i >= 0; --i)
{
if(postorder[i] > root) return false;
while(!stk.empty() && postorder[i] < stk.top()){
root = stk.top();
stk.pop();
}
stk.push(postorder[i]);
}
return true;
}
};