题目
方法一-递归
算法思路
通过递归,判断所有的子树的正确性,即后序遍历序列是否满足二叉搜索树的定义,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
设后续遍历序列的区间左右边界分别为 l e f t left left 和 r i g h t right right,遍历到的当前节点索引为 c u r cur cur
- 终止条件: l e f t > = r i g h t left\ >\,=\ right left >= right ,说明此子树节点数最多为1,无需判断正确性,直接返回 t r u e true true
- 递推过程:
Step1:划分左右子树: 遍历后续遍历序列的 [ l e f t , r i g h t ] [left\ ,\ right] [left , right] 区间,寻找第一个大于根节点的节点,记索引为 m m m ,则左子树区间为 [ l e f t , m − 1 ] [left\ ,\ m-1] [left , m−1],右子树区间为 [ m , r i g h t − 1 ] [m\ ,\ right-1] [m , right−1],根节点索引为 r i g h t right right
Step2:判断是否为二叉搜索树:
对于左子树区间:在划分左右子树时已经确保左子树中所有节点的值均小于根节点;
对于右子树区间:遍历该区间,当遇到小于等于根节点的节点时跳出。 - 返回值(用 && 连接):
Part1: c u r = r i g h t cur = right cur=right:判断此树是否正确
Part2: r e c u r ( l e f t , m − 1 ) recur(left,\ m-1) recur(left, m−1):判断左子树是否正确
Part3: r e c u r ( m , r i g h t − 1 ) recur(m,\ right-1) recur(m, right−1):判断右子树是否正确
具体代码
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
private boolean recur(int[] postorder, int left, int right){
if(left >= right)return true;
int cur = left;//当前节点索引
while(postorder[cur] < postorder[right])cur++;
int m = cur;//第一个大于根节点的节点索引
while(postorder[cur] > postorder[right])cur++;
return cur == right && recur(postorder, left, m - 1) && recur(postorder, m, right - 1);
}
}
复杂度分析
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),n 为二叉树节点总数。每次调用 r e c u r ( ) recur() recur() 会减去一个根节点,因此递归占用 O ( n ) O(n) O(n);最坏情况下,即,树退化为链表时,每一轮递归都要遍历树的所有节点,占用 O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n),最坏情况下,即,树退化为链表时,递归的深度会达到 n
方法二-辅助单调栈
算法思路
后序遍历序列的倒序,可以看作前序遍历的镜像,即“根——右——左”的顺序。
设后序遍历序列为
[
r
n
,
r
n
−
1
,
.
.
.
,
r
1
]
[r_n,r_{n-1},...,r_1]
[rn,rn−1,...,r1],遍历该序列,设当前节点索引为
i
i
i,若该树为二叉搜索树,则有:
当遇到递增节点,即
r
i
>
r
i
+
1
r_i>r_{i+1}
ri>ri+1 时:节点
r
i
r_i
ri 一定是节点
r
i
+
1
r_{i+1}
ri+1的右子节点。
当遇到递减节点,即
r
i
<
r
i
+
1
r_i<r_{i+1}
ri<ri+1 时:节点
r
i
r_i
ri 一定是某个节点
r
o
o
t
root
root 的左子节点,该节点
r
o
o
t
root
root 是左边的节点
r
n
,
r
n
−
1
,
.
.
.
,
r
i
+
2
,
r
i
+
1
r_n,r_{n-1},...,r_{i+2},r_{i+1}
rn,rn−1,...,ri+2,ri+1 中 大于
r
i
r_i
ri且最接近
r
i
r_i
ri 的节点。此外,在
r
i
r_i
ri 右边的所有节点的值,均小于
r
o
o
t
root
root 。
若在遍历后序遍历序列的整个过程中,遇到的所有递减节点的父节点均满足上述条件,则说明该树是二叉搜索树。
因此,考虑利用递增单调栈实现(需要通过比较前后元素的大小关系来解决问题时通常使用的方法):
每当遇到递减节点时,通过出栈来更新该节点的父节点
r
o
o
t
root
root,每一轮判断该节点
r
i
r_i
ri 和
r
o
o
t
root
root 的值的关系,若
r
i
>
r
o
o
t
r_i>root
ri>root说明不满足二叉搜索树定义,直接返回
f
a
l
s
e
false
false,若
r
i
<
r
o
o
t
r_i<root
ri<root则继续往下遍历。
算法流程:
1. 初始化:单调栈
s
t
k
stk
stk ,父节点
r
o
o
t
root
root 的值初始化为正无穷大
2. 倒序遍历
p
o
s
t
o
r
d
e
r
postorder
postorder,记当前节点为
r
i
r_i
ri:
Step1:判断。若
r
i
>
r
o
o
t
r_i>root
ri>root 则说明不满足二叉搜索树定义,直接返回
f
a
l
s
e
false
false;
Step2:更新父节点
r
o
o
t
root
root。当栈不为空,且,
r
i
<
s
t
k
.
p
e
e
k
(
)
r_i<stk.peek()
ri<stk.peek()时,循环执行出栈,并将出栈节点赋给
r
o
o
t
root
root;
Step3:入栈。将当前节点
r
i
r_i
ri 入栈
3. 返回值:若遍历完成,则返回
t
r
u
e
true
true
具体代码
class Solution {
public boolean verifyPostorder(int[] postorder) {
Deque<Integer> stk = new LinkedList<>();//单调栈初始化
int root = Integer.MAX_VALUE;//父节点值初始化
//倒序遍历
for(int i = postorder.length - 1; i >= 0; i--){
if(postorder[i] > root)return false;
//更新父节点
while(!stk.isEmpty() && postorder[i] < stk.peek()){
root = stk.pop();
}
stk.push(postorder[i]);//当前节点入栈
}
return true;
}
}
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),n 为二叉树节点总数。因为遍历所有节点,每个节点都入栈\出栈一次。
- 空间复杂度: O ( n ) O(n) O(n),最坏情况下,单调栈会同时存储所有节点,使用 O ( n ) O(n) O(n) 额外空间。