题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。
如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
解题思路:
方法一:单调栈法O(n)(比较巧,初次接触会不太好理解,对照图来看,一定能看懂!)
-
首先我们知道后续遍历序列为:
[ 左 | 右 | 根 ]
—> -
现在将后续遍历倒过来,即后序遍历的倒序为:
[ 根 | 右 | 左 ]
<—- 是不是有点类似 先序遍历 的镜像 ;即先序遍历为
根、左、右
的顺序,而后序遍历的倒序为根、右、左
顺序。
- 是不是有点类似 先序遍历 的镜像 ;即先序遍历为
-
那我们就仿照按照先序遍历的思想:后续遍历的倒序遍历思想为:先遍历根,再遍历右,最后遍历左
-
所以:
- 因为二叉搜索树的大小关系:
左<根<右
- 所以在这种后续遍历倒序模式下,任何局部应该都是:从根开始,往右深入(值变大),再往左深入(值变小)
- 故我们来看:续遍历的倒序序列,是不是每次都先往右深入,然后是左,即都是值先变大,在再变小
- 因为二叉搜索树的大小关系:
有了以上的基本认识以后,我们来看如何检查这个序列是否是后续遍历的倒序:
1、从左往右遍历,如果p[i]
< p[i+1]
, 就往后走;
2、直到遇到第一个p[i]
> p[i+1]
,即上图的6>2,这个时候,2 一定是某个root 的左孩子!
2.1、为什么呢,上面我们讨论了,这个遍历思想是值是先增大后减小,一旦减小说明开始遍历到左孩子了!
2.2、既然遍历到左孩子了,就说明,序列中2 的右边的所有元素(图上1,3)都一定比 2 的父亲(root,5)要小!
2.3 那如果其2右边的序列(1,3)比root(5)要大
,显然就不符合这个遍历思想,那么就返回 false
!
好了,这个方法的核心思想讲完了:刚接触的人肯定还是一头雾水,你可以再看一遍我的解析,弄清楚上面的核心遍历思想!
下面我们来看如何将这个判别思想流程化:
- 初始化一个辅助栈
stack
,我们首先将 root 设置为Integer.MAX_VALUE
- 题目给的后序遍历序列(非倒序)postorder,那我们就 从后往前 遍历, 假设当前节点为 p[i]
- 若
p[i] > root
,即不满足条件二叉搜索树右子< 根
的思想,返回false
- 若
p[i] < root
且 stack 不为空:循环出栈,将出栈元素赋值给 root - 若遍历完成,说明为 true
- 若
//代码
class Solution {
public boolean verifyPostorder(int[] postorder) {
//辅助栈
Stack<Integer> stack = new Stack<>();
int root = Integer.MAX_VALUE;
//从后往前遍历
for(int i = postorder.length - 1; i >= 0; i--) {
//当前节点大于当前的root,false
if(postorder[i] > root) return false;
//遇到当前节点小于栈顶的元素,就pop(),然后赋值给root,直到栈为空或者栈顶大于当前元素
while(!stack.isEmpty() && stack.peek() > postorder[i])
{root = stack.pop();}
//一般情况,就入栈
stack.add(postorder[i]);
}
return true;
}
}
参考链接(大佬有图解):
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/
方法二:递归分治(常规)
-
首先我们知道一颗二叉搜索树的后续遍历的结构:
左 || 右 || 根
-
一颗二叉树搜索树在任何局部都满足的的大小关系为:
左 < 根 < 右
-
所以我们想到一个简单的检验是否为后续遍历序列的方法:
- 假设后续遍历序列为
【R1,R2,R3...Ri, R(i+1),...Rn,root】
- 我们从左往右遍历,由于左边均为左子树,即满足
Rx < root
; - 一旦找到第一个大于root的点,假设下标为i ,即
Ri > root
; - 这说明从Ri开始,后面的序列应该全是root的右子树的节点, 且
右 > 根
即Rx > root
,若后面的序列【Ri…Rn】中存在一个不满足 Rx > root , 返回 false
通过以上检查后,我们递归的去检查更小的分支是否符合;
即 检查 左子树【R1,R2... Ri-1】
和右子树【Ri...Rn】
check(int[] post , start ,i-1)
&&check(int[] post , i, end)
class Solution { public boolean verifyPostorder(int[] postorder) { if( postorder.length==0) return true; return verifyPostorder(postorder,0,postorder.length-1); } public boolean verifyPostorder(int[] post, int start , int end ){ if(end <= start ) return true; //找到第一个大于根节点的点 post[i] int i =0; for( ; i < end ; i++){ if(post[i]> post[end]) break; } //检查 post[i] 后面的序列是否存在小于root的节点,如存在,返回false for(int j = i ; j < end ; j++){ if(post[j]<post[end]) return false; } //递归的去检查 左子树 和 右子树 return verifyPostorder(post,start,i-1) && verifyPostorder(post,i,end-1) ; } }
- 假设后续遍历序列为