这个专栏一年多没更新了,奈何看到这道剑指上面的题有两种非常非常巧妙的非递归解法,在此记录并分享一下(dalao是用C++写的,我改成了Python)。
问题:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。
思路:这道题是一道经典的二叉树后序遍历题,一般拿到这种题,先去想递归解法:
- 后序序列的顺序是左右根,因此数组的最后一位必然是根;
- 找到根之后,比根小的部分就是左子树,比根大的部分就是右子树;
- 递归判断左子树和右子树是否为BST。
递归法的关键在于寻找递归出口:
- 出口1:如果递归到空树,说明判断完了,那么返回True即可
- 出口2:找到根结点的值之后,可以划分出左右子树,如果左右子树的结点数加起来不等于全部结点数-1(即右子树右边还有比根小的值),那么这棵树则不是BST,返回False
递归解法如下:
# -*- coding:utf-8 -*-
结果报错:maximum recursion depth exceeded,果断爆栈了,看来需要优化为非递归算法。
因为右子树所有值一定比左子树所有值都大,因此每次判断完一次之后,剔除原本的根,则倒数第二个结点就是剩下所有结点的根!因此再循环判断剩下的结点即可。
代码如下
class
时间复杂度:O(n^2), 空间复杂度:O(1),算是一种时间换空间以及可读性的方法
这个方法非常优雅,可读性强,但是一般来说,CPU资源比内存资源宝贵的多,有没有空间换时间的解法呢——二叉树的非递归后序遍历通常双栈法来实现(几种遍历里面最复杂的),这里我们就用栈来辅助,最终达到O(n)的时间复杂度和O(n)的空间复杂度。
分析:根节点实际上对左右子树形成了一个上下限约束,即左子树的上限、右子树的下限,如果我们从右往左倒序遍历这个序列,则访问顺序为:根右左
0. 首先,用一个栈来存储根;
- 如果当前访问的元素>栈顶,则说明当前元素可能是栈顶的右儿子,这个时候我们判断:
- 如果当前元素>约束上限,则说明其祖辈中存在左子树>根的情况,返回False
- 否则,当前元素就是栈顶的右儿子
- 如果当前元素<栈顶,则说明当前元素一定是栈中某个元素(不一定是栈顶)的左儿子,这个时候我们需要找出它的父节点,它的父亲一定是栈中比它大的最小元素,同时将这个父节点的右子树中的结点全部出栈,并将该父节点的值作为新的约束上限。
2. 然后将当前元素入栈。
代码实现如下:
class
复杂度分析:空间自然为O(n),同时每个元素至少入栈一次,至多出栈一次,因此时间为O(n)。这个解法很巧妙,但是理解起来很困难,可以看看dalao的原文,里面有图解:
《剑指Offer》二叉搜索树的后序遍历序列_牛客博客blog.nowcoder.net