剑指offer刷题--GZ23--二叉搜索树的后续遍历序列

在这里插入图片描述
解题思路:
从O(nlogn)到O(n),比递归效率更高的方法:上限约束法

方法一:递归法
看了评论,发现大家普遍使用的是递归分割法,递归简单易懂容易实现,先来一次遍历以确定出左右子树的分界点,然后再分别对两棵子树进行递归判断。现在让我们来分析一下递归方法的时间复杂度:

以标准的完美二叉搜索树为例,递归的每一层都涉及到对序列的遍历,虽然层数越深节点越少(少了子树的根节点),但是这种减少是微不足道的,即使是到了最底层,依旧有n/2的节点(完美二叉树第i层节点数是其上所有节点数之和+1),因此递归方法在每一层的遍历开销是O(n),而对于二叉树而言,递归的层数平均是O(logn),因此,递归方法的最终复杂度是O(nlogn)

方法二:上限约束法
那么贪婪的我们不禁就会想,有没有更好的方法??O(n)、甚至O(logn)?

由于一个正确的遍历序列,我们可以在它任意一个位置故意篡改,因此势必要遍历所有的元素才能确定它的正确性,所以个人认为,这个问题的时间复杂度下界应该就是O(n)。

具体来说,二叉搜索树的关键特征是:对于任意一棵子树,均有“左子树<根节点<右子树”,因此,它的根节点约束了它左右子树的取值范围,二叉搜索树的根root是它左子树值的上限(max),同时是它右子树值的下限(min)。如果我们从根节点出发往下走,那么高层祖辈节点序列就会不停地对低层未遍历节点形成一个上下限约束,只要低层节点没有违背这个约束,那么它就是合法的,否则,序列就是不合法的。

因为后序遍历的一些特性,我们可以从右到左倒序访问给定的序列,因为后序遍历的最后一个元素是根节点,倒序访问就相当于是从根到叶,从右到左的访问顺序,从根到叶可以让我们利用祖辈节点提供的上下限约束信息来判断孩子节点合法性,具体步骤如下:

如果当前元素 > 上一个元素,这说明当前元素可能是上一个元素的右孩子,这时候:
如果当前元素突破max上限约束,说明祖辈中存在 “左子树 > 根” 的情况,违背了搜索树的定义(尝试把图中4的值换成7,那么就有子辈7 > 祖辈max约束5,搜索树不成立);
否则,当前元素就是上一个元素的右孩子,当前元素将成为新的祖辈节点,为后续节点提供约束;
如果当前元素 < 上一个元素,这就说明当前元素是某个祖辈节点的左孩子,这时候:
我们需要找出这个祖辈节点,并将该祖辈的右子树节点全部丢弃(因为它的右子树结构已经确定,无法为后续节点提供帮助),该祖辈节点的值将成为新的max上限约束,
当前元素将成为新的祖辈节点,继续为后续节点提供约束;
在这里插入图片描述
因此,我们需要使用一个栈来存储已经确定了的祖辈节点,当新节点的合法性被确定时,入栈,当算法需要寻找当前节点是谁的左孩子时,出栈;

复杂度分析:在没有出栈的情况下,序列依次从右到左访问,信息依次入栈,每次也只访问栈顶的值,复杂度为O(n);在需要出栈的情况下,由于同一个节点不会反复入栈出栈,所以,最坏情况就是所有节点都要经历出栈操作,复杂度由O(n)变为O(2n)。忽略常数,算法的最终时间复杂度是O(n),空间复杂度也是O(n)。

import java.util.Stack;
public class Solution {
    
    
    public boolean VerifySquenceOfBST(int [] sequence) {
    
    
        if(sequence.length < 1){
    
    
            return false;
        }
        //roots栈中依次存放各层父辈节点的值
        //事先放入一个值避免对空栈进行判断
        Stack<Integer> roots = new Stack();
        roots.push(Integer.MIN_VALUE);
        int max = Integer.MAX_VALUE;
        for(int i = sequence.length-1; i > -1; i--){
    
    
            //如果当前节点超过max约束,那它必定不是二叉树
            if(sequence[i] >= max){
    
    
                return false;
            }
            //如果当前节点小于roots的栈顶,说明该节点是某个主辈的左孩子,需要找出这个祖辈
            //也就是不断出栈,利用二叉搜索树的特点,找出该祖辈,同时,该主辈也提供的新的max约束
            while(sequence[i] < roots.peek()){
    
    
                max = roots.peek();
                //为了找出给节点的祖辈而出栈
                roots.pop();
            }
            // 该节点成了新一代的祖辈节点,为后续节点判断自己的位置提供依据
            roots.push(sequence[i]);
        }
        return true;
    }
}

转载于:https://blog.nowcoder.net/n/8fe97e67996249ccbe71328d3a49c4af?f=comment

猜你喜欢

转载自blog.csdn.net/weixin_42118981/article/details/113288631