二叉排序树的后序遍历序列必然是递增的_剑指offer 33——二叉搜索树的后序遍历序列...
本題主要在于考察對二叉搜索樹和后序遍歷的理解。
原題
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷結果。如果是則返回 true,否則返回 false。假設輸入的數組的任意兩個數字都互不相同。
參考以下這顆二叉搜索樹:
5 / 2 6 / 1 3示例 1:
輸入: [1,6,3,2,5]輸出: false示例 2:
輸入: [1,3,2,6,5]輸出: true提示:
- 數組長度 <= 1000
原題url:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
解題
基本概念
首先介紹一些基本概念,方便后續做題。
遞歸分治
既然本題只提供了后序遍歷,那么我們就要在此基礎之上下功夫了。
根據上面的提供的說明,后序遍歷是,先左右子樹再根節點,那么根是容易判斷的,肯定在整個序列的最后。
而二叉搜索樹節點值滿足,左子樹 < 根 < 右子樹,因此我們就可以以根為基礎,從后向前遍歷,。
一開始的節點肯定都大于根,因為都在右子樹上,一旦出現小于根的節點,說明就進入了左子樹,那么之后所有的節點都應該小于根。然后再分別遍歷左右子樹,直至到葉子節點為止(即無左右子樹的節點)。
按照上面的方法,就需要我們將后序遍歷分成左右子樹,不斷遞歸遍歷檢查。
接下來看看代碼:
class Solution { public boolean verifyPostorder(int[] postorder) { return checkTree(postorder, 0, postorder.length - 1); } private boolean checkTree(int[] postorder, int start, int end) { // 如果start >= end,說明已經尋找結束 if (start >= end) { return true; } // 找到根 int root = postorder[end]; // 左子樹開始的下標 int leftStart = start; // 左子樹結束的下標 int leftEnd = leftStart; // 找到第一個大于根節點的值 while (leftEnd < end && postorder[leftEnd] < root) { leftEnd++; } leftEnd--; // 右子樹開始的下標 int rightStart = leftEnd + 1; // 右子樹結束的下標 int rightEnd = end - 1; // 檢查右子樹是否都大于根節點 for (int i = rightStart; i < end; i++) { if (postorder[i] > root) { continue; } return false; } // 繼續檢查左右子樹 return checkTree(postorder, leftStart, leftEnd) && checkTree(postorder, rightStart, rightEnd); }}提交OK。
分析一下復雜度:
- 時間復雜度 O(N^2) : 每次調用 checkTree 方法減去一個根節點,因此遞歸占用 O(N) ;最差情況下(即當樹退化為鏈表),每輪遞歸都需遍歷樹所有節點,占用 O(N ^ 2) 。
- 空間復雜度 O(N) : 最差情況下(即當樹退化為鏈表),遞歸深度將達到 N 。
遞增棧
既然上面分析出時間復雜度為 O(N^2) ,那么是否可以找到一種更高效的方法,只遍歷一次序列,就可以解決問題呢?因為這樣可以在時間復雜度上進行很大的優化。
這就需要再進一步結合搜索二叉樹和后序遍歷的特性了。(這個方法我是在網上看到的,感覺屬于一種比較偏門的優化,一般很難想出這種方法)
在我們從后向前遍歷序列時,大致是經歷了根、右子樹、左子樹,而左子樹 < 根 < 右子樹,那么一開始應該是單調遞增的,我們可以將這些節點依次入棧。
當不滿足單調遞增調試時,一般是碰到了右子樹中某一個左子樹節點,或者真正的左子樹,這時候可以將棧頂元素出棧,直到碰到比當前節點小的元素,那么將最后的棧頂元素設為根節點。
此時繼續遍歷,應該保證所有節點都小于根節點,因為此時已經進入左子樹序列了。否則說明該序列不滿足搜索二叉樹的后序遍歷。
重復以上步驟,如果遍歷結束,說明滿足搜索二叉樹的后序遍歷。
這么說可能比較難懂,直接上代碼:
class Solution { public boolean verifyPostorder(int[] postorder) { // 單調遞增棧 Stack stack = new Stack<>(); int root = Integer.MAX_VALUE; // 倒序遍歷 for (int i = postorder.length - 1; i >= 0; i--) { if (postorder[i] > root) { return false; } // 如果當前棧不為空,且當前遍歷的節點小于棧頂節點 while (!stack.isEmpty() && postorder[i] < stack.peek()) { // 棧頂節點壓出,且更新根節點 root = stack.pop(); } // 當前節點入棧 stack.push(postorder[i]); } return true; }}提交OK。
分析一下復雜度:
- 時間復雜度 O(N) : 遍歷 postorder 所有節點,各節點均入棧 / 出棧一次,使用 O(N) 時間。
- 空間復雜度 O(N) : 最差情況下(即當樹退化為鏈表),單調遞增棧 stack 存儲所有節點。
神奇的是,力扣給出的執行結果顯示:遞歸分治方法消耗的時間更短。這點大家也可以研究研究是為什么。
總結
以上就是這道題目我的解答過程了,不知道大家是否理解了。本題主要在于考察對二叉搜索樹和后序遍歷的理解,遞歸分治是容易想出來的方法,但是后面那種單調遞增棧確實很難想到,可以作為一種特殊思路進行理解。
有興趣的話可以訪問我的博客或者關注我的公眾號、頭條號,說不定會有意外的驚喜。
https://death00.github.io/
公眾號:健程之道
總結
以上是生活随笔為你收集整理的二叉排序树的后序遍历序列必然是递增的_剑指offer 33——二叉搜索树的后序遍历序列...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ 1936 字符匹配(水题)
- 下一篇: LeetCode 908. 最小差值 I