剑指–二叉搜索树的后序遍历序列
1,题目:
2,思路:
方法一:递归分治:
解题思路:
- 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
- 二叉搜索树定义: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。
递归解析:
- 1.终止条件: 当 i≥j ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true ;
- 2.递推工作:
- 3.划分左右子树: 遍历后序遍历的 [i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 m。此时,可划分出左子树区间 [i,m−1] 、右子树区间 [m,j−1] 、根节点索引 j 。
- 4.判断是否为二叉搜索树:
- 5.左子树区间 [i,m−1] 内的所有节点都应 <postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
- 6.右子树区间 [m,j−1] 内的所有节点都应 >postorder[j] 。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过 p=j 判断是否为二叉搜索树。
- 7.返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符 && 连接。
- 8.p=j : 判断 此树 是否正确。
- 9.recur(i,m−1) : 判断 此树的左子树 是否正确。
- 10.recur(m,j−1) : 判断 此树的右子树 是否正确。
下面是对应的图解:
方法二:辅助单调栈:
(这个方法二如果是自己写是写不出来的)
算法流程:
- 1.初始化: 单调栈 stack ,父节点值root=+∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
- 2.倒序遍历 postorder :记每个节点为ri;
- 3.判断: 若ri>root ,说明此后序遍历序列不满足二叉搜索树定义,直接返回 false ;
- 4.更新父节点 root : 当栈不为空 且ri<stack.peek() 时,循环执行出栈,并将出栈节点赋给root 。
- 5.入栈: 将当前节点ri入栈;
- 6.若遍历完成,则说明后序遍历满足二叉搜索树定义,返回 true 。
下面是对应的图解:
3,代码:
方法一:递归分治:
class Solution {
public boolean verifyPostorder(int[] postorder) {
/*
递归分治:
解题思路:
后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。
递归解析:
1.终止条件: 当 i≥j ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true ;
2.递推工作:
3.划分左右子树: 遍历后序遍历的 [i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 m。此时,可划分出左子树区间 [i,m−1] 、右子树区间 [m,j−1] 、根节点索引 j 。
4.判断是否为二叉搜索树:
5.左子树区间 [i,m−1] 内的所有节点都应 <postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
6.右子树区间 [m,j−1] 内的所有节点都应 >postorder[j] 。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过 p=j 判断是否为二叉搜索树。
7.返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符 && 连接。
8.p=j : 判断 此树 是否正确。
9.recur(i,m−1) : 判断 此树的左子树 是否正确。
10.recur(m,j−1) : 判断 此树的右子树 是否正确。
*/
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;//这样p就指向了比根节点大的第一个节点(其实也就是右子树的第一个节点)
int m = p;
while(postorder[p] > postorder[j]) p++;//这时候这个循环是找的右子树,这时候p已经指向了最后的节点,也就是根节点
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);//recur(postorder, i, m - 1)这个是循环左子树了。recur(postorder, m, j - 1)这个是循环右子树了。左子树和右子树也是这样的方法。。。慢慢的会再循环更小的左子树和右子树
}
}
方法二:辅助单调栈:
class Solution {
public boolean verifyPostorder(int[] postorder) {
/*
算法流程:
1.初始化: 单调栈 stack ,父节点值root=+∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
2.倒序遍历 postorder :记每个节点为ri;
3.判断: 若ri>root ,说明此后序遍历序列不满足二叉搜索树定义,直接返回 false ;
4.更新父节点 root : 当栈不为空 且ri<stack.peek() 时,循环执行出栈,并将出栈节点赋给root 。
5.入栈: 将当前节点ri入栈;
6.若遍历完成,则说明后序遍历满足二叉搜索树定义,返回 true 。
*/
Stack<Integer> stack = new Stack<>();
int root = Integer.MAX_VALUE;
for(int i = postorder.length - 1; i >= 0; i--) {
if(postorder[i] > root) //因为此时的root是无穷大,但是postorder[i] > root说明是不符合逻辑的,所以返回false
return false;
while(!stack.isEmpty() && stack.peek() > postorder[i])
root = stack.pop();//更新root节点
stack.add(postorder[i]);
}
return true;
}
}