题目
输出一个整数数组,判断该数组是不是某二叉搜寻树的后序遍历后果。如果是则返回 true,否则返回 false。假如输出的数组的任意两个数字都互不雷同。
参考以下这颗二叉搜寻树:
示例 1:
输出: [1,6,3,2,5]
输入: false
示例 2:
输出: [1,3,2,6,5]
输入: true
准备知识点
二叉树的遍历
二叉树的深度优先遍历可细分为前序遍历、中序遍历、后序遍历,这三种遍历能够用递归实现,也可应用非递归实现。
前序遍历:根节点->左子树->右子树(根->左->右)
中序遍历:左子树->根节点->右子树(左->根->右)
后序遍历:左子树->右子树->根节点(左->右->根)
1. 前序遍历
前序的遍历的特点,根节点->左子树->右子树,留神看前序的遍历的代码printf语句是放在两条递归语句之前的,所以先拜访根节点G,打印G,而后拜访左子树D,此时左子树D又作为根节点,打印D,再拜访D的左子树A
A又作为根节点,打印A,A没有左子树或者右子树,函数调用完结返回到D节点(此时曾经打印进去的有:GDA)D节点的左子树曾经递归实现,当初递归拜访右子树F,F作为根节点,打印F,F有左子树拜访左子树E,E作为
根节点,打印E,(此时曾经打印进去的有:GDAFE),E没有左子树和右子树,函数递归完结返回F节点,F的左子树曾经递归实现了,但没有右子树,所以函数递归完结,返回D节点,D节点的左子树和右子树递归全副实现,
函数递归完结返回G节点,拜访G节点的右子树M,M作为根节点,打印M,拜访M的左子树H,H作为根节点,打印H,(此时曾经打印进去的有:GDAFEMH)H没有左子树和右子树,函数递归完结,返回M节点,M节点的左子树曾经
递归实现,拜访右子树Z,Z作为根节点,打印Z,Z没有左子树和右子树,函数递归完结,返回M节点,M节点的左子树右子树递归全副实现,函数递归完结,返回G节点,G节点的左右子树递归全副实现,整个二叉树的遍历就完结了
前序遍历后果:GDAFEMHZ
总结一下前序遍历步骤
第一步:打印该节点
第二步:拜访左子树,返回到第一步(留神:返回到第一步的意思是将根节点的左子树作为新的根节点,就好比图中D是G的左子树然而D也是A节点和F节点的根节点)
第三步:拜访右子树,返回到第一步
第四步:完结递归,返回到上一个节点
前序遍历的另一种表述:
(1)拜访根节点
(2)前序遍历左子树
(3)前序遍历右子树
(在实现第2,3步的时候,也是要依照前序遍历二叉树的规定实现)
2. 中序遍历(具体遍历过程就不再赘述了,(┬_┬))
第一步:拜访该节点左子树
第二步:若该节点有左子树,则返回第一步,否则打印该节点
第三步:若该节点有右子树,则返回第一步,否则完结递归并返回上一节点
(按我本人了解的中序就是:先左到底,左到不能在左了就停下来并打印该节点,而后返回到该节点的上一节点,并打印该节点,而后再拜访该节点的右子树,再左到不能再左了就停下来)
中序遍历的另一种表述:
(1)中序遍历左子树
(2)拜访根节点
(3)中序遍历右子树
(在实现第1,3步的时候,要依照中序遍历的规定来实现)
所以该图的中序遍历为:ADEFGHMZ
3. 后序遍历步骤
第一步:拜访左子树
第二步:若该节点有左子树,返回第一步
第三步:若该节点有右子树,返回第一步,否则打印该节点并返回上一节点
后序遍历的另一种表述:
(1)后序遍历左子树
(2)后序遍历右子树
(3)拜访根节点
(在实现1,2步的时候,仍然要依照后序遍历的规定来实现)
该图的后序遍历为:AEFDHZMG
网上多找一些重构二叉树的练习题,有助于加深了解上述三种遍历树的过程
重要概念
二叉搜寻树特色:左子树小于根节点小于右子树
题解思路
了解上述知识点后,回顾题目,既然是后序遍历,那么,数组最初一个肯定是根节点,例如给你[1,3,2,6,5]
这个数组,那么5就是根节点,依照二叉搜寻树的特色,1,3就是左子树,2,6,5就是右子树。同时每个子树也要满足上诉条件,自然而然就想到递归查找判断了。明确了这些原理后,用代码实现就很容易了。
代码实现
/** * @param {number[]} postorder * @return {boolean} */var verifyPostorder = function(postorder) { if (!postorder.length) return true; // 如果没有子树了,返回true const root = postorder[postorder.length - 1]; // 后续遍历后果的最初一位是树的根节点 // 根节点的左子树全副小于根节点,根节点的右子树全副大于根节点 const leftTree = [], rightTree = []; // 缓存以后节点的左子树和右子树,用于下次递归查问判断 let cIndex = null; // 记录左右子树的分界点,即第一个满足右子树的节点,因为左右子树要离开递归查 const flag = postorder.every((item, index) => { if (index === postorder.length - 1) return true; // 根节点不判断 if (cIndex === null && item > root) { cIndex = index; // 记录第一个遇到大于根节点的右子树节点,作为分界线 } if (cIndex !== null) { // 如果cIndex !== null,阐明曾经遍历到右子树的节点了,那就要依照右节点的规定判断接下去的树(右子树的值全都要大于根节点),并且把之后的节点push到右子树的数组,用于递归 if (index >= cIndex) { rightTree.push(item); return item > root; } } else { // 如果cIndex === null就说名没有遇到右子树的节点,那就判断左子树的特色,左子树的节点都要小于根节点 leftTree.push(item); return item < root; } }) if (!flag) return false; // 以后节点都没有满足搜寻的特色,那就间接返回false了 let left = true, right = true; if (leftTree.length) { left = verifyPostorder(leftTree); // 递归查问判断左子树是否满足二叉搜寻树的特色 } if (rightTree.length) { right = verifyPostorder(rightTree); // 递归查问判断右子树是否满足二叉搜寻树的特色 } return left && right; // 左右都满足二叉搜寻树的特色,才会返回true};