某米的面试,我前面的回答都正确了,这是第一次到了算法题环节,总共面试了55分钟,哦,耶,一个巨大的进步。
判断数组是否为二叉搜索树后序遍历的结果
我用的方法是递归,但是递归肯定不是最佳答案。递归的思路很简单,对于数组先从右到左遍历,以最末尾元素为二叉树的root,然后向左找,一直找到比root小的元素。在这个位置把数组拆分为左子树和右子树。只需要校验左子树不存在比root大的元素就可以了。再继续以左子树为子数组,右子树为子数组进行递归判断。以下面这棵树为例子:
后序遍历顺序为2, 4, 3, 6, 8, 7, 5。
递归方案的代码是:
public class ArrayTest {
public static void main(String[] args) {
/*
* 5
* 3 7
*2 4 6 8
* 2 4 3 6 8 7 5
*/
int[] array = {2, 4, 3, 6, 8, 7, 5};
System.out.println(recursive(array, 0, array.length));
}
public static boolean recursive(int[] array, int start, int end) {
// 递归结束点
if (end - start <= 2) {
return true;
}
final int root = array[end - 1];
int i = end - 2;
for (; i >= start; i--) {
if (array[i] < root) {
break;
}
}
final int middle = i;
for (; i >= start; i--) {
if (array[i] >= root) {
return false;
}
}
return recursive(array, start, middle) && recursive(array, middle, end - 1);
}
}
但是有没有更好的方案呢?我们知道所有的递归都可以用栈来代替。而后序遍历用的也是双栈算法。把后序遍历的双栈反过来肯定不行,因为无法区分边界。所以需要再仔细分析需求,需求的最重要的就是检验右子树不能有比root小或左子树不能有比root大的。其实递归实际上也是用栈来实现,java中就是虚拟机栈。不过虚拟机栈,由于栈的每一层或者叫帧,保存了局部变量、方法引用等信息,所以开销比较大。如果用栈代替递归来实现,这时候的栈就是数据栈了,没一层只保存少量的数据,而这个栈使用的是堆内的内存,不用担心会有虚拟机栈溢出的风险。我们可以考虑用DFS来实现。如下图,就不是一棵二叉搜索树:
这棵树的后序遍历顺序是2, 9, 3, 6, 8, 7, 5。首先我们确定右子树是6、8、7。因为左边两个都比root小,可以看成,都是左子树,都比root大可以看成右子树。判断的在于左子树不能出现比root大的元素,这种情况就是root、右子树、左子树点、某个比root大的元素。右子树可以没有,于是最小的不符合的情况就是类似这种,6,4,5。
我们可以根据递归的本质,去构造递归树。以下是按数组索引构建的递归树:
根据这颗递归树,而这也恰恰是日常开发过程中一种最常见的用循环代替递归的方法,就是将参数作为栈的帧,来代替虚拟机的栈。也就是和虚拟机的栈保持一致,但是少了方法引用,并减少了公共参数。使用DFS写出的代码如下:
public class CheckPostRecursive {
public static void main(String[] args) {
int[] array = {2, 4, 3, 6, 8, 7, 5};
System.out.println(check(array));;
}
private static boolean check(int[] array) {
Deque<int[]> indices = new ArrayDeque<>(array.length);
indices.add(new int[]{0, array.length-1});
while (!indices.isEmpty()) {
final int[] pop = indices.pop();
final int end = pop[1];
final int start = pop[0];
int root = array[end];
int leftChildEnd = end - 1;
for (; leftChildEnd >= start; leftChildEnd--) {
if (array[leftChildEnd] < root) {
break;
}
}
for (int i = start; i < leftChildEnd; i++) {
if (array[i] > root) {
return false;
}
}
if (end > start) {
indices.push(new int[]{start, leftChildEnd});
indices.push(new int[]{leftChildEnd + 1, end - 1});
}
}
return true;
}
}