Loading

Medium | 剑指 Offer 33. 二叉搜索树的后序遍历序列 | 递归 | 单调栈

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

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

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

     5
    / \
   2   6
  / \
 1   3

示例 1:

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

示例 2:

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

提示:

  1. 数组长度 <= 1000

方法一: 递归

题目给了一个后序遍历的数组, 后序遍历的最后的一个元素就是树的根节点, 根据二叉搜索树的特点, 左子树全部小于根节点, 右子树全部大于根节点。所以只需要从左往右扫描, 找到第一个大于根节点的元素的下标, 那么这个元素的左边的所有元素, 就是当前树的根节点的左子树, 剩下的部分就是当前根节点的右子树。然后再分别递归判断左右子树是否是一个二叉搜索树即可。

递归过程可以进行剪枝:

由于是通过从左往右扫描, 找到第一个大于根节点的元素的下标找到左子树的, 找到的这些数必然小于根节点值, 此时扫描剩下的部分(也就是右子树), 如果其中有小于根节点值的, 则可以直接返回false

public boolean verifyPostorder(int[] postorder) {
    return verifyPostorder(postorder, 0, postorder.length - 1);
}

public boolean verifyPostorder(int[] postorder, int start, int end) {
    if (start >= end) {
        return true;
    }
    int rootVal = postorder[end];
    int leftBorder = start;
    // 找左子树的有边界
    while(postorder[leftBorder] < rootVal) {
        leftBorder++;
    }
    // 剪枝:如果右子树有值小于根节点值, 直接返回true
    while (postorder[leftBorder] > rootVal) {
        leftBorder++;
    }
    if (leftBorder < end) {
        return false;
    }
    // 递归判断左右子树是否是二叉搜索树
    return verifyPostorder(postorder, start, leftBorder - 1) && verifyPostorder(postorder, leftBorder, end - 1);
}

方法二:单调栈

大牛的解法就是简单却又难以理解 -> 失火的夏天的题解

思路非常巧妙, 首先用到了一个后序遍历的结果数组的特点, 其倒过来是一个类似于先序遍历的镜像, 倒过来看就是先遍历根节点, 然后遍历右子树, 最后遍历左子树。

我们先将数组倒序, 这样就变成了一个NRL遍历, 然后从左往右扫描数组, 如果是递增的节点, 我们就将其作为右孩子, 如果是一个变小的数, 则要找到他的父节点, 将其作为左孩子, 并且往后所有的元素的值, 都是要小于这个父节点的, 因为是NRL的遍历方式。如果之后有个元素值大于找到的这个父节点, 那么这个数就不是一个二叉树。

初次见到这个过程不太容易理解, 建议自己动手画一画, 每遍历一个元素就在树上加一个节点, 先感受一下整体的思路过程。

那么核心问题就在于, 我怎么才能找到这个父节点呢?

答案是借助一个单调递增栈。

从左往右扫描数据, 如果当前扫描元素大于栈顶元素, 则代表当前节点是其双亲节点的右子树, 直接压栈即可。如果扫描到一个元素小于栈顶元素, 则说明当前节点是其双亲节点的左孩子, 然后我们找他的父节点, 那么只需要不断出栈即可, 直到栈顶元素小于当前节点值, 那么此轮出栈过程, 最后一个出栈节点就是当前节点的父节点, 那么数组后面元素的所有值, 都要小于这个父节点的值, 如果有大于这个父节点的值的, 直接返回false。

public boolean verifyPostorder(int[] postorder) {
    // 单调栈使用,单调递增的单调栈
    Deque<Integer> stack = new LinkedList<>();
    // 表示上一个根节点的元素,这里可以把postorder的最后一个元素root看成无穷大节点的左孩子
    int pervElem = Integer.MAX_VALUE;
    // 逆向遍历,就是翻转的先序遍历
    for (int i = postorder.length - 1;i>=0;i--){
        // 左子树元素必须要小于递增栈被peek访问的元素,否则就不是二叉搜索树
        if (postorder[i] > pervElem){
            return false;
        }
        while (!stack.isEmpty() && postorder[i] < stack.peek()){
            // 数组元素小于单调栈的元素了,表示往左子树走了,记录下上个根节点
            // 找到这个左子树对应的根节点,之前右子树全部弹出,不再记录,因为不可能在往根节点的右子树走了
            pervElem = stack.pop();
        }
        // 这个新元素入栈
        stack.push(postorder[i]);
    }
    return true;
}
posted @ 2021-01-15 12:11  反身而诚、  阅读(106)  评论(0编辑  收藏  举报