递归(二)二叉搜索树的后序遍历序列
问题描述
输入一个整数数组,判断该数组是否是某个二叉树的后序遍历结果,如果是则返回 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)\)
-