每日一题 - 剑指 Offer 33. 二叉搜索树的后序遍历序列
题目信息
-
时间: 2019-06-26
-
题目链接:Leetcode
-
tag:二叉搜索树 分治算法 递归
-
难易程度:中等
-
题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回
true
,否则返回false
。假设输入的数组的任意两个数字都互不相同。
参考如下:
5
/ \
2 6
/ \
1 3
示例1:
输入: [1,6,3,2,5]
输出: false
示例2:
输入: [1,3,2,6,5]
输出: true
提示
1.数组长度<=1000
解题思路
本题难点
- 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
- 二叉搜索树定义: 左子树中所有节点的值< 根节点的值;右子树中所有节点的值 <根节点的值;其左、右子树也分别为二叉搜索树。
具体思路
根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
-
终止条件:当 i≥j ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true ;
-
递推工作:
-
划分左右子树:遍历后序遍历的 [i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为
m。此时,可划分出左子树区间[i,m−1] 、右子树区间[m,j−1] 、根节点索引 j -
判断是否为二叉搜索树
左子树区间[i,m−1] 内的所有节点都应 <postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
右子树区间[m,j−1] 内的所有节点都应postorder>[j] 。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过p=j 判断是否为二叉搜索树。
-
代码
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder,0,postorder.length-1);
}
public boolean recur(int[] postorder,int left,int right){
//当 i≥j ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true
if(left > right){
return true;
}
//划分左右子树
int p = left;
//划分出左子树区间[i,m−1]
while(postorder[p] < postorder[right]){
p++;
}
//寻找第一个大于根节点的节点,索引记为 m
int m = p;
//右子树区间[m,j−1]
while(postorder[p] > postorder[right]){
p++;
}
//左子树区间[i,m−1] 内的所有节点都应 <postorder[j]
//右子树区间[m,j−1] 内的所有节点都应postorder[j]
return p == right && recur(postorder,left,m-1) && recur(postorder,m,right-1);
}
}
复杂度分析:
- 时间复杂度 O(N^2) : 每次调用 recur(i,j) 减去一个根节点,因此递归占用 O(N) ;最差情况下(即当树退化为链表),每轮递归都需遍历树所有节点,占用 O(N) 。
- 空间复杂度 O(N) : 最差情况下(即当树退化为链表),递归深度将达到 N 。
其他优秀解答
解题思路
辅助单调栈,后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。
-
为什么要用单调栈呢,因为往右子树遍历的过程,value是越来越大的,一旦出现了value小于栈顶元素value的时候,就表示要开始进入左子树了。
-
单调栈帮我们记录了这些节点,只要栈顶元素还比当前节点大,就表示还是右子树,要移除,因为我们要找到这个左孩子节点直接连接的父节点,也就是找到这个子树的根,只要栈顶元素还大于当前节点,就要一直弹出,直到栈顶元素小于节点,或者栈为空。栈顶的上一个元素就是子树节点的根。
-
接下来,数组继续往前遍历,之后的左子树的每个节点,都要比子树的根要小,才能满足二叉搜索树,否则就不是二叉搜索树。
代码
class Solution {
public boolean verifyPostorder(int[] postorder) {
// 单调栈使用,单调递增的单调栈
Deque<Integer> stack = new LinkedList<>();
// 表示上一个根节点的元素,这里可以把postorder的最后一个元素root看成无穷大节点的左孩子
int pervElem = Integer.MAX_VALUE;
// 逆向遍历,就是翻转的先序遍历
for (int i = postorder.length - 1;i>=0;i--){
// 左子树元素必须要小于递增栈被peek访问的元素,否则就不是二叉搜索树
if (postorder[i] > pervElem){
return false;
}
while (!stack.isEmpty() && postorder[i] < stack.peek()){
// 数组元素小于单调栈的元素了,表示往左子树走了,记录下上个根节点
// 找到这个左子树对应的根节点,之前右子树全部弹出,不再记录,因为不可能在往根节点的右子树走了
pervElem = stack.pop();
}
// 这个新元素入栈
stack.push(postorder[i]);
}
return true;
}
}