递归(二)二叉搜索树的后序遍历序列

对应 剑指 Offer 33 二叉树的后序遍历序列

问题描述

输入一个整数数组,判断该数组是否是某个二叉树的后序遍历结果,如果是则返回 true,否则返回 false

说明:

  • 数组的长度 \(\leq1000\)

  • 数组中的任意两个数组都不相同

解决思路

假设这里输入的数组为 \(post\)

  • 递归

    解决树相关的问题首选递归,假设定义的递归方法为 recur(int lo, int hi),其中 lo 表示待处理的数组的最低位置索引,hi 表示待处理的数组的最高位置索引,该方法用于判断区间 \([lo, hi]\) 中的元素是否是某个二叉树搜索树的后序遍历

    • \(lo \geq hi\) 时,说明此时数组中只存在一个节点,这种情况下肯定是某个二叉搜索树的后序遍历,因此返回 true

    • 由于是后序遍历,因此整数数组的最后一个元素(如果是二叉搜索树)一定是当前区间所在的根节点。此时遍历 \([lo, hi - 1]\) 区间,找到第一个大于 \(post[hi]\) 的数据元素,该数据元素一定是当前区间的根节点的右子节点。此时,该元素对应的索引位置设为 \(m\),因此可以划分区间 \([lo, m - 1]\)\([m, hi - 1]\) 为继续递归处理的区间

    • 左子树区间 \([lo, m - 1]\) 中的数据元素都应该 \(<post[hi]\),右子树区间 \([m, hi - 1]\) 内的所有节点都应该 \(>post[hi]\)

  • 单调栈

    首先定义一个概念:“后序遍历的倒序” 为按照 “根、右、左” 的顺序进行遍历。

    假设以此方式遍历之后的列表为 \([r_n, r_{n-1},\cdots,r_1]\),设当前在某个位置进行遍历,索引为 \(i\),如果该遍历结果是一棵二叉搜索树的遍历结果,那么:

    • 如果 \(r_i > r_{i + 1}\),那么节点 \(r_i\) 一定是节点 \(r_{i + 1}\) 的右子节点

    • 如果 \(r_i < r_{i + 1}\),那么节点 \(r_{i}\) 一定是某个节点的左子节点,并且该节点一定是 \(r_{i + 1}\), \(r_{i + 2}\), \(\cdots\), \(r_{n}\) 中最接近 \(r_i\) 的节点(因为 \(r_i\) 直接连接了对应的父节点)

    • 如果 \(r_i < r_{i + 1}\),那么在树为二叉搜索树的情况下,对于 \(r_i\) 右边的任意节点 \(r_x\in[r_{i - 1}, r_{i - 2}, \cdots, r_1]\),必定有节点值 \(r_x < r_i\)

    基于以上的分析,在从后向前遍历的过程中存在递减的关系,因此可以考虑通过引入单调栈来解决这个问题:

    • 单调栈 \(stack\) 维护从后向前遍历值递增的元素

    • 每当遇到递减的节点 \(r_i\),则通过出栈的方式来更新节点 \(r_i\) 的父节点 \(root\)

    • 每轮判断 \(r_i\)\(root\) 之间的关系:

      • 如果 \(r_i > root\),那么说明并不满足二叉搜索树的定义,直接返回 \(false\)

      • 如果 \(r_i < root\),那么说明满足二叉搜索树的定义,继续遍历

  • 模拟

    如果能够通过该数组构建一棵二叉搜索树,那么这个数组 \(post\) 就是一个合法的二叉排序树

    实际上并不需要去真正构建一棵二叉搜索树,只需要判断节点能否被正常完全消耗即可

    注意: 由于后序遍历时通过 “左、右、根” 的顺序来访问的,因此重新构建时需要按照相反的顺序,即 “根、右、左” 的顺序来进行构建

实现

  • 递归

    class Solution {
        int[] post;
        int n;
    
        public boolean verifyPostorder(int[] _post) {
            post = _post; n = post.length;
    
            return dfs(0, n - 1);
        }
    
        boolean dfs(int left, int hi) {
            if (left >= hi) return true;
            int p = left;
            while (post[p] < post[hi]) p++;
            int m = p;
            while (post[p] > post[hi]) p++;
    
            return p == hi && dfs(left, m - 1) && dfs(m, hi - 1);
        }
    }
    

    复杂度分析:

    • 时间复杂度:\(O(n^2)\)

    • 空间复杂度:忽略由于递归带来的开销,空间复杂度为 \(O(1)\)

  • 单调栈

    class Solution {
        public boolean verifyPostorder(int[] post) {
            Stack<Integer> stack = new Stack<>();
            // 初始设置为最大值,使得正确的根节点能够入栈
            int root = Integer.MAX_VALUE;
    
            for (int i = post.length - 1; i >= 0; --i) {
                if (post[i] > root) return false;
                while (!stack.isEmpty() && stack.peek() > post[i]) 
                    root = stack.pop();
                stack.push(post[i]);
            }
    
            return true;
        }
    }
    

    复杂度分析:

    • 时间复杂度:\(O(n)\)

    • 空间复杂度:由于引入了辅助的单调栈,因此空间复杂度为 \(O(n)\)

  • 模拟

    class Solution {
        int[] post;
        int end, MAX = Integer.MAX_VALUE, MIN = Integer.MIN_VALUE;
    
        public boolean verifyPostorder(int[] _post) {
            post = _post;
            if (post.length == 1) return true;
            end = post.length - 1;
            build(MIN, MAX);
    
            return end < 0;
        }
    
        void build(int min, int max) {
            if (end < 0) return;
            int r = post[end];
            if (r >= max || r <= min) return;
            end--;
            build(r, max);
            build(min, r);    
        }
    }
    
  • 复杂度分析:

    • 时间复杂度:\(O(n)\)

    • 空间复杂度:\(O(1)\)


参考:
[1] 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/

posted @ 2022-03-02 14:55  FatalFlower  阅读(22)  评论(0编辑  收藏  举报