• 每日一题


    题目信息

    • 时间: 2019-06-26

    • 题目链接:Leetcode

    • tag:二叉搜索树 分治算法 递归

    • 难易程度:中等

    • 题目描述:

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

    参考如下:

         5
        / 
       2   6
      / 
     1   3
    

    示例1:

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

    示例2:

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

    提示

    1.数组长度<=1000
    

    解题思路

    本题难点

    • 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
    • 二叉搜索树定义: 左子树中所有节点的值< 根节点的值;右子树中所有节点的值 <根节点的值;其左、右子树也分别为二叉搜索树。

    具体思路

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

    • 终止条件:当 ij ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true ;

    • 递推工作

      • 划分左右子树:遍历后序遍历的 [i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为
        m。此时,可划分出左子树区间[i,m−1] 、右子树区间[m,j−1] 、根节点索引 j

      • 判断是否为二叉搜索树

        左子树区间[i,m−1] 内的所有节点都应 <postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。

        右子树区间[m,j−1] 内的所有节点都应postorder>[j] 。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过p=j 判断是否为二叉搜索树。

    代码

    class Solution {
        public boolean verifyPostorder(int[] postorder) {
            return recur(postorder,0,postorder.length-1);
        }
        public boolean recur(int[] postorder,int left,int right){
          //当 i≥j ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true
            if(left > right){
                return true;
            }
          //划分左右子树
            int p = left;
          //划分出左子树区间[i,m−1] 
            while(postorder[p] < postorder[right]){
                p++;
            }
          //寻找第一个大于根节点的节点,索引记为 m
            int m = p;
          //右子树区间[m,j−1] 
            while(postorder[p] > postorder[right]){
                p++;
            }
          //左子树区间[i,m−1] 内的所有节点都应 <postorder[j] 
          //右子树区间[m,j−1] 内的所有节点都应postorder[j] 
            return p == right && recur(postorder,left,m-1) && recur(postorder,m,right-1);
        }
    }
    

    复杂度分析:

    • 时间复杂度 O(N^2) : 每次调用 recur(i,j) 减去一个根节点,因此递归占用 O(N) ;最差情况下(即当树退化为链表),每轮递归都需遍历树所有节点,占用 O(N) 。
    • 空间复杂度 O(N) : 最差情况下(即当树退化为链表),递归深度将达到 N

    其他优秀解答

    解题思路

    辅助单调栈,后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。

    • 为什么要用单调栈呢,因为往右子树遍历的过程,value是越来越大的,一旦出现了value小于栈顶元素value的时候,就表示要开始进入左子树了。

    • 单调栈帮我们记录了这些节点,只要栈顶元素还比当前节点大,就表示还是右子树,要移除,因为我们要找到这个左孩子节点直接连接的父节点,也就是找到这个子树的根,只要栈顶元素还大于当前节点,就要一直弹出,直到栈顶元素小于节点,或者栈为空。栈顶的上一个元素就是子树节点的根。

    • 接下来,数组继续往前遍历,之后的左子树的每个节点,都要比子树的根要小,才能满足二叉搜索树,否则就不是二叉搜索树。

    代码

    class Solution {
        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;
        }
    }
    
  • 相关阅读:
    Leetcode Array 4 Median of Two Sorted Arrays
    vscode Python Pylint(代码检测插件)
    Leetcode Array 1 twoSum
    mysql 配置 安装和 root password 更改
    vscode 编译调试c/c++的环境配置
    chm文件打不开的解决办法
    A + B Problem II
    欢天喜地七仙女——代码规范与计划
    欢天喜地七仙女——项目系统设计与数据库设计
    欢天喜地七仙女——团队Gitee实战训练
  • 原文地址:https://www.cnblogs.com/ID-Wangqiang/p/13218616.html
Copyright © 2020-2023  润新知