# 剑指 Offer 33. 二叉搜索树的后序遍历序列

大家好,我是吴师兄。

今天继续来学习《剑指Offer》系列的一道经典题目,依旧给出了非常详细的题解和精美的配图与动画。

# 一、题目描述

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

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false

示例 2:

输入: [1,3,2,6,5]
输出: true

提示:

  1. 数组长度 <= 1000

# 二、题目解析

这道题目首先得掌握以下基础概念:

  • 1、二叉搜索树
  • 2、后序遍历的递归写法

二叉搜索树是一棵有序的二叉树,它具有如下的性质:

  • 1、若它的左子树不为空,那么左子树上的所有值均小于它的根节点
  • 2、若它的右子树不为空,那么右子树上所有值均大于它的根节点
  • 3、它的左子树和右子树也都是二叉搜索树

而后序遍历的递归过程是 左 ---》右 ---》根 的顺序。

比如对于下面这棵二叉搜索树来说,它的后序遍历序列是 [4, 8, 6, 12, 16, 14, 10]

由于后序遍历的过程中是不断的执行 左 ---》右 ---》根 的顺序,那也就意味着结果数组的最后一个数字必然是二叉搜索树的根节点

如果这个数组是二叉搜索树的后序遍历结果,那么在这个根节点 10 的前面既有它的左子树又有它的右子树,并且左右子树是扎堆出现的,即小于 10 的那些节点是连续排列的,大于 10 的那些节点也是连续排列的。

因此,我们可以从头到尾去遍历 10 前面的数字,找到第一个大于 10 的位置,这个位置前面的数都小于了 10 ,是它的左子树,这个位置后面的数都大于了 10,是它的右子树。

也就是如上图所示,划分为了青、黄、绿三个区域。

只要在黄色区域里面发现了小于 10 的值,那么就知道它不是二叉搜索树了

比如后序遍历序列 [4, 8, 6, 12, 16, 9, 10]

12 前面的都小于了 10 ,是 10 的左子树,12 后面的数字都是 10 的右子树,但对于二叉搜索树来说,右子树所有的节点都应该大于根节点的值,而 9 < 10 ,因此这棵二叉树不是二叉搜索树。

接下来,我们来看一下完整的操作:

1、获取数组最后一个节点的值,它是树的根节点,此时节点值为 10。

2、从头到尾遍历数组,直到遍历到第一个大于根节点 10 的值为止。

3、遍历过程中,发现 12 这个节点是第一个大于根节点 10 的值。

4、如果它是一棵二叉搜索树,那么从 12 开始,后面所有的节点值都应该大于 10,如果出现一个小于 10 的值,那么就不是二叉搜索树,直接返回 false 就行,否则,整个数组就被划分为三个区域。

5、接下来,依次递归的判断左子树(青色区域)、右子树(黄色区域)是否也是二叉搜索树。

为了帮助你更好的理解整个过程,我特意做了一组动画,点开可以查看

#

# 三、参考代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 剑指 Offer 33. 二叉搜索树的后序遍历序列:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
class Solution {
    public boolean verifyPostorder(int[] postorder) {

        // 递归的调用 isPostorder ,判断任意一棵子树是否是二叉搜索树
        return isPostorder( postorder , 0 , postorder.length - 1);
    }

    private boolean isPostorder(int[] postorder , int left , int right ){

        // 当 left >= right 时,说明当前区间只有一个节点或者没有节点了
        // 不需要在判断下去
        if( left >= right) return true;

        // 先去后去当前区间最后一个节点的值
        // 由于是后序遍历,即采取 左、右、根的顺序遍历
        // 那么这个节点实际上是根节点
        int rootNodeValue = postorder[right];

        // 接下来,需要在这个区间里找出 rootNode 的左子树与右子树
        int firstRightNodeIndex = left;

        // 从左到右遍历当前区间,找到第一个大于 rootNodeValue 的位置
        // 这个位置前面的数都小于了 rootNodeValue ,是它的左子树
        // 这个位置后面的数都大于了 rootNodeValue ,是它的右子树
        while( postorder[firstRightNodeIndex] < rootNodeValue ){
            firstRightNodeIndex += 1;
        }
        
        // 接下来判断从 firstRightNodeIndex 到 right 这个区间中,是否存在比 rootNodeValue 更小的值
        int curNodeIndex = firstRightNodeIndex;

        while( curNodeIndex < right){
            curNodeIndex += 1;
            
            // 如果发现从 firstRightNodeIndex 到 right 这个区间中存在比 rootNodeValue 更小的值
            // 根据二叉搜索树的性质,它的右子树的所有节点都必须大于根节点
            // 此时,不符合这个性质,意味着这不是一棵二叉搜索树
            if( postorder[curNodeIndex] < rootNodeValue ){

                return false;

            }
        }

        // 当代码执行到这里的时候,rootNode 的左子树值都小于了 rootNodeValue
        // rootNode 的右子树值都大于了 rootNodeValue
        // 接下来,递归的判断它的左子树、右子树是否是二叉搜索树
        return isPostorder( postorder , left , firstRightNodeIndex - 1 ) 
               && 
               isPostorder( postorder , firstRightNodeIndex , right - 1);
    }
}