【JZ-33】二叉搜索树的后序遍历序列(树、分治算法、栈)

题目

在这里插入图片描述参考

方法一-递归

算法思路

通过递归,判断所有的子树的正确性,即后序遍历序列是否满足二叉搜索树的定义,若所有子树都正确,则此序列为二叉搜索树的后序遍历。

设后续遍历序列的区间左右边界分别为 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 , m1],右子树区间为 [ m   ,   r i g h t − 1 ] [m\ ,\ right-1] [m , right1],根节点索引为 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, m1):判断左子树是否正确
    Part3: r e c u r ( m ,   r i g h t − 1 ) recur(m,\ right-1) recur(m, right1):判断右子树是否正确

具体代码

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,rn1,...,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,rn1,...,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) 额外空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值