Problem
输入: 一个整数数组(假设输入的数组的任意两个数字都互不相同)
任务:判断该数组是不是某二叉搜索树的后序遍历结果
返回:布尔值
递归分治
思路
划分左右子树
遍历数组arr [i, j]区间元素,寻找第一个大于根节点的节点arr[x] ,划分左子树区间 [i,x-1]右子树区间 [x, j - 1],根节点 j
判断是否满足二叉搜索树条件
条件: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值
因左子树必满足,只需判断右子树
终止条件
i≥j,遍历结束
answer
/**
* @param {number[]} arr
* @return {boolean}
*/
var verifyPostorder = function(arr) {
if(!arr) return false;
return recur(arr, 0, arr.length-1);
};
// 定义递归函数
var recur = (arr, i, j) => {
if(i>=j) return true;
let x = i;
while (arr[x] < arr[j]) x++; // 寻找第一个大于根结点的位置
for(let k = x; k<j; k++){
if(arr[k] < arr[j]) return false; // 右子树只要出现小于根结点的节点就中断不符合要求
}
return recur(arr, i, x-1) && recur(arr, x,j-1)
}
辅助单调栈
思路
为了方便分析,将后续遍历结果倒着看:
后续遍历结果:[3,6,5,9,8,11,13,12,10] ==> [10,12,13,11,8,9,5,6,3]
arr[i]<arr[i+1]
挨着的两个数如果arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点。
因为比arr[i]大的肯定都是他的右子节点,如果还是挨着他的,肯定是在后续遍历中所有的右子节点最后一个遍历的,所以他一定是arr[i]的右子节点。
arr[i]>arr[i+1]
如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个值是大于arr[i+1]中最小的。
比如13,11是降序的,那么11肯定是他前面某一个节点的左子节点,并且这个值是大于11中最小的,我们看到12和13都是大于11的,但12最小,所以11就是12的左子节点。
辅助栈
综上,可使用栈来解决
遍历数组的所有元素,如果栈为空,就把当前元素压栈。如果栈不为空,并且当前元素大于栈顶元素,那么就说明当前元素是栈顶元素的右子节点,把当前元素压栈。当前元素小于栈顶元素,说明当前元素是某个节点的左子节点,我们目的是要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点。
answer
/**
* @param {number[]} postorder
* @return {boolean}
*/
var verifyPostorder = function(arr) {
if(!arr) return false;
let stack = [], parent = Math.max;
for(let i = arr.length-1; i>=0; i--){
//当如果前节点小于栈顶元素,左子树,找父节点[知道找到最小的大于自身的数]
while(stack.length>0 && arr[i] < stack[stack.length-1]) { // 注意此时同层级的右子树必然已经在栈里了
parent = stack.pop();
}
if(arr[i] > parent) { //难理解结合图看
return false;
}
stack.push(arr[i])
}
return true;
};
拓展
题目改为判断该数组是不是某二叉搜索树的后序遍历结果?
只需要判断数组是否有序就行了