1.题目
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
2.我的题解
二叉搜索树:一个二叉树中,任一节点的左子树的所有值都小于该节点的值,而右子树的所有值都大于该节点的值,则该二叉树称之为二叉搜索树。
根据二叉搜索树的性质进行递归求解。
class Solution {
bool IsBST(vector<int> data,int l,int r){
if(l>=r)return true;
int pivot = data[r];
int i=l;
for(;i<r;i++)
if(data[i]>pivot)break;
int mid=i;
for(;i<r;i++)
if(data[i]<pivot)return false;
return IsBST(data,l,mid-1) && IsBST(data,mid,r-1);
}
public:
bool VerifySquenceOfBST(vector<int> sequence) {
int len = sequence.size();
if(len==0)return false;
return IsBST(sequence,0,len-1);
}
};
3.别人的题解
3.1最大最小边界法
链接:https://www.nowcoder.com/questionTerminal/a861533d45854474ac791d90e447bafd?answerType=1&f=discussion
来源:牛客网
以下为复制粘贴内容。
方法二:最大最小边界约束法
那么贪婪的我们不禁就会想,有没有更好的方法??O(n)、甚至O(logn)?
由于一个正确的遍历序列,我们可以在它任意一个位置故意篡改,因此势必要遍历所有的元素才能确定它的正确性,所以个人认为,这个问题的时间复杂度下界应该就是O(n),当然,思路往往是频繁出错的,如果您有更好的解决思路,或者发现了本文方法的不周之处,请务必告知于我,一起学习,共同进步!!!
我们的思路是,在对序列进行遍历时,能否仅根据元素当前的递增递减趋势,以及过往历史留下的信息记录,就可以对序列的合法性作出判断呢?二叉搜索树的关键特征是:对于任意一棵子树,均有“左子树<根节点<右子树”,我们发现,这一关键特征可以被转化为另一形式的表达,即:对于任一棵子树,它的根节点约束了它左右子树的取值范围,子树的根root是它左子树值的上限(max),同时是它右子树值的下限(min)。那么如果我们从根节点出发往下走,那么高层祖辈节点序列就会不停地对低层未遍历节点形成一个上下限约束,只要低层节点没有违背这个约束,那么它就是合法的,否则,序列可能就是不合法的(也可能是合法的,后文讨论)。
因为后序遍历的一些特性,我们决定从右到左倒序访问给定的序列,因为后序遍历的最后一个元素是根节点,这时倒序访问就相当于是从根到叶,从右子树到左子树的访问顺序,从根到叶可以满足我们利用祖辈节点约束上下限来判断孩子节点合法性的需求。
如果当前元素 > 上一个元素(倒序递增),这就说明当前元素是上一个元素的右孩子,这时候:
如果当前元素突破max上限约束,说明祖辈中存在 “左子树 > 根” 的情况,违背了搜索树的定义(尝试把图中4的值换成7,那么就有子辈7 > 祖辈max约束5,搜索树不成立)
否则,当前元素落在上下限之间,是合法的,利用上一个元素更新上下限min,max
如果当前元素 < 上一个元素(倒序递减),这就说明当前元素在上一个元素的下方或左方,这时候:
如果当前元素打破min下限约束,则在左方,我们在这里称之为树跳转(右子树访问完了,跑到左子树去了),那么我们需要将已有的min,max记录清空到两棵树的共同父节点为止(因为两棵树所处的分支不同,上下限也要重新开始计算,如图中的 6->3 ),虽然打破了约束,但是这种情况却是合法的
否则,当前元素落在上下限之间,是上一个元素的左孩子,利用上一个元素更新上下限min,max
那么,我们使用三个栈来存储上下限信息,root栈、min栈、和max栈,之所以要用栈,是因为正常情况下从根到叶走,我们只需要使用最新的min,max约束就可以了,但是,如果遇到了树跳转的情况,当前元素 < min,那么就需要使用历史信息了,将三个栈元素出栈,直到当前元素 > min。root栈主要是用来保存父节点值,用来出栈后再入栈(进入左子树更新max)。初始时,需要把正负无穷这一默认上下限入栈。
复杂度分析:如果没有出栈的情况(树跳转),那么序列依次从右到左访问,信息依次入栈,每次也只访问栈顶的值,复杂度为O(n);如果出现了树跳转,那么需要将右子树产生的信息全部出栈,但是同一个节点不会出现反复入栈出栈的情况,所以,最坏情况就是所有节点都要经历出栈操作,复杂度由O(n)变为O(2n)。忽略常数,最终算法的时间复杂度是O(n),空间复杂度也是O(n)
以上为复制粘贴内容。
举例:2,4,3,6,9,8,5
sequence | root(stack) | min(stack) | max(stack) |
---|---|---|---|
5 | 5 | MIN | MAX |
8 | 5,8 | MIN,5 | MAX,MAX |
9 | 5,8,9 | MIN,5,5 | MAX,MAX,MAX |
6 | 5,8,9,6 | MIN,5,5,5 | MAX,MAX,MAX,9 |
3 | 5,3 | MIN,MIN | MAX,5 |
4 | 5,3,4 | MIN,MIN,3 | MAX,5,5 |
2 | 5,3,2 | MIN,MIN,MIN | MAX,5,3 |
class Solution {
stack<int> min,max,root;
public:
bool VerifySquenceOfBST(vector<int> sequence) {
int len = sequence.size();
if(len==0)return false;
min.push(INT_MIN);
max.push(INT_MAX);
root.push(sequence[len-1]);
for(int i=len-2;i>=0;i--){
//右子树
if(sequence[i]>sequence[i+1]){
if(sequence[i]>max.top())return false;
max.push(max.top());
min.push(root.top());
root.push(sequence[i]);
}
else{//左子树
//弹出之前的右子树及根
while(sequence[i]<min.top()){
root.pop();
max.pop();
min.pop();
}
max.push(root.top());
min.push(min.top());
root.push(sequence[i]);
}
}
return true;
}
};
3.2partition
二叉搜索树后续遍历:一段小值+一段大值+中间值
快排中的partition:一段小值+中间值+一段大值
咦?这俩好像一样啊!假如是二叉搜索树后续遍历序列的话,那么对其进行partition
操作就不会改变该序列。
注: 尽量使用稳定的partition
操作,不稳定的partition
会改变原有序列数字的相对位置,可能引发某些问题,稳定的则不会。
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
int len = sequence.size();
if(len==0)return false;
int root = sequence[len-1];
vector<int> vec=sequence;
stable_partition(vec.begin(),vec.end(),[root](int x){return x<root;});
return vec==sequence;
}
};