剑指(33):二叉搜索树的后续遍历序列

本文介绍两种高效算法来判断一个整数数组是否为二叉搜索树的后序遍历结果,包括巧妙的单调栈法和递归分治策略,详细解析算法原理并附带代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。
如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

解题思路:

方法一:单调栈法O(n)(比较巧,初次接触会不太好理解,对照图来看,一定能看懂!)
  • 首先我们知道后续遍历序列为:[ 左 | 右 | 根 ] —>

  • 现在将后续遍历倒过来,即后序遍历的倒序[ 根 | 右 | 左 ] <—

    • 是不是有点类似 先序遍历 的镜像 ;即先序遍历根、左、右 的顺序,而后序遍历倒序根、右、左 顺序。
  • 那我们就仿照按照先序遍历的思想:后续遍历的倒序遍历思想为:先遍历,再遍历,最后遍历

  • 所以:

    • 因为二叉搜索树的大小关系:左<根<右
    • 所以在这种后续遍历倒序模式下,任何局部应该都是:从根开始,往右深入(值变大),再往左深入(值变小
    • 故我们来看:续遍历的倒序序列,是不是每次都先往右深入,然后是左,即都是值先变大,在再变小

在这里插入图片描述

有了以上的基本认识以后,我们来看如何检查这个序列是否是后续遍历的倒序:

1、从左往右遍历,如果p[i] < p[i+1] , 就往后走;

2、直到遇到第一个p[i]> p[i+1] ,即上图的6>2,这个时候,2 一定是某个root 的左孩子!

​ 2.1、为什么呢,上面我们讨论了,这个遍历思想是值是先增大后减小,一旦减小说明开始遍历到左孩子了!

​ 2.2、既然遍历到左孩子了,就说明,序列中2 的右边的所有元素(图上1,3)都一定比 2 的父亲(root,5)要小!

​ 2.3 那如果其2右边的序列(1,3)比root(5)要大,显然就不符合这个遍历思想,那么就返回 false

好了,这个方法的核心思想讲完了:刚接触的人肯定还是一头雾水,你可以再看一遍我的解析,弄清楚上面的核心遍历思想!

下面我们来看如何将这个判别思想流程化:

  1. 初始化一个辅助栈stack ,我们首先将 root 设置为Integer.MAX_VALUE
  2. 题目给的后序遍历序列(非倒序)postorder,那我们就 从后往前 遍历, 假设当前节点为 p[i]
    1. p[i] > root ,即不满足条件二叉搜索树右子< 根的思想,返回false
    2. p[i] < rootstack 不为空:循环出栈,将出栈元素赋值给 root
    3. 若遍历完成,说明为 true
//代码
class Solution {
    public boolean verifyPostorder(int[] postorder) {
        //辅助栈
        Stack<Integer> stack = new Stack<>();
        int root = Integer.MAX_VALUE;
        //从后往前遍历
        for(int i = postorder.length - 1; i >= 0; i--) {
            //当前节点大于当前的root,false
            if(postorder[i] > root) return false;
            //遇到当前节点小于栈顶的元素,就pop(),然后赋值给root,直到栈为空或者栈顶大于当前元素
            while(!stack.isEmpty() && stack.peek() > postorder[i])
            	{root = stack.pop();}
            //一般情况,就入栈
            stack.add(postorder[i]);
        }
        return true;
    }
}

参考链接(大佬有图解):

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/mian-shi-ti-33-er-cha-sou-suo-shu-de-hou-xu-bian-6/

方法二:递归分治(常规)
  • 首先我们知道一颗二叉搜索树的后续遍历的结构:左 || 右 || 根

  • 一颗二叉树搜索树在任何局部都满足的的大小关系为:左 < 根 < 右

  • 所以我们想到一个简单的检验是否为后续遍历序列的方法:

    • 假设后续遍历序列为【R1,R2,R3...Ri, R(i+1),...Rn,root】
    • 我们从左往右遍历,由于左边均为左子树,即满足Rx < root
    • 一旦找到第一个大于root的点,假设下标为i ,即Ri > root
    • 这说明从Ri开始,后面的序列应该全是root的右子树的节点, 且右 > 根
      Rx > root ,若后面的序列【Ri…Rn】中存在一个不满足 Rx > root , 返回 false

    通过以上检查后,我们递归的去检查更小的分支是否符合;
    即 检查 左子树 【R1,R2... Ri-1】和右子树【Ri...Rn】
    check(int[] post , start ,i-1) && check(int[] post , i, end)

    class Solution {
    
    public boolean verifyPostorder(int[] postorder) {
        if( postorder.length==0) return true;
        return verifyPostorder(postorder,0,postorder.length-1);
    }
    
    public boolean verifyPostorder(int[] post, int start , int end ){
        if(end <= start ) return true;
        //找到第一个大于根节点的点 post[i]
        int i =0;
        for( ; i < end ; i++){
            if(post[i]> post[end]) break;  
        }
    	//检查 post[i] 后面的序列是否存在小于root的节点,如存在,返回false
        for(int j = i ;  j < end ; j++){
            if(post[j]<post[end]) return false;
        }
    	//递归的去检查 左子树 和 右子树
        return verifyPostorder(post,start,i-1) 
            && verifyPostorder(post,i,end-1) ;
    }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值