剑指offer面试题33:二叉树的后序遍历序列

题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

示例 1:输入: [1,6,3,2,5]     输出: false

示例 2:输入: [1,3,2,6,5]     输出: true

解题思路:
后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
二叉搜索树定义: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。

方法一:递归分治
根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
递归解析:
终止条件: 当 left >= right ,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true ;
递推工作:
(1)划分左右子树: 遍历后序遍历的 [left,right] 区间元素,寻找 第一个大于根节点 的节点,索引记为 k 。此时,可划分出左子树区间 [left,k-1] 、右子树区间 [k,right-1]、根节点索引 right 。
(2)判断是否为二叉搜索树:
          1) 左子树区间 [left,k-1] 内的所有节点都应 < postorder[right] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
          2)右子树区间 [k,right-1] 内的所有节点都应 > postorder[right] 。实现方式为遍历,当遇到 ≤postorder[right] 的节点则跳出;则可通过 k == right 判断是否为二叉搜索树。
(3)返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符 && 连接。
1.  k == right: 判断 此树 是否正确。
2.  recur(left, k - 1) : 判断 此树的左子树 是否正确。
3.  recur(k, right - 1) : 判断 此树的右子树 是否正确。

class Solution {
public:
 // 要点:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是
    bool verifyPostorder(vector<int>& postorder) 
    {
        //后序遍历:left -> right -> root
        //二叉搜索树:left < root < right
        //if(postorder.size() <= 2) return true;
        int right = postorder.size() - 1;       
        return recur(postorder,0,right);   
    }

    // 递归实现
    bool recur(vector<int>& tree,int left,int right)
    {   
        //如果left==right,就一个节点不需要判断了,如果left>right说明没有节点,
        //也不用再看了,否则就要继续往下判断
        if(left >= right) return true;  
        int k = left;
        int rootVal = tree[right];  // 当前树的根节点的值
        
        // 从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
        while(k < right && tree[k] < rootVal)
        {
            k++;
        }
        // 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接退出
        while(k < right && tree[k] > rootVal)
        {
            k++;
        }
        
        // 当前树遍历完所有节点没问题,就检查左右子树
        return k == right && recur(tree,left,k-1) && recur(tree,k,right-1);  
    }
};
class Solution {
public:
 // 要点:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是
    bool verifyPostorder(vector<int>& postorder) 
    {
        //后序遍历:left -> right -> root
        //二叉搜索树:left < root < right
        if(postorder.size() < 2) return true;
        //int right = postorder.size() - 1;       
        return recur(postorder,0,postorder.size() - 1);   
    }

    // 递归实现
    bool recur(vector<int>& tree,int left,int right)
    { 
        //如果left==right,就一个节点不需要判断了,如果left>right说明没有节点,
        //也不用再看了,否则就要继续往下判断   
        if(left >= right) return true;  
        int k = left;
        int rootVal = tree[right];  // 当前树的根节点的值
        
        // 从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
        while(k < right && tree[k] < rootVal)
        {
            k++;
        }
        // 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接返回false
        for(int i=k;i<right;i++)
        {
            if(tree[i] < rootVal) return false;
        }
        
        // 当前树遍历完所有节点没问题,就检查左右子树
        return recur(tree,left,k-1) && recur(tree,k,right-1);  
    }
};

方法二:辅助单调栈
后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。

当遍历时遇到递减节点 r_i < r_{i+1} ,若为二叉搜索树,则对于后序遍历中节点 r_i右边的任意节点 ,必有节点值 <root。

遍历 “后序遍历的倒序” 会多次遇到递减节点 r_i,若所有的递减节点 r_i  对应的父节点 root都满足以上条件,则可判定为二叉搜索树。

根据以上特点,考虑借助 单调栈 实现:
1、借助一个单调栈 stackstack 存储值递增的节点;
2、每当遇到值递减的节点 r_i ,则通过出栈来更新节点 r_i  的父节点 rootroot ;
3、每轮判断 r_i 和 root 的值关系:
         (1)若 r_i > root 则说明不满足二叉搜索树定义,直接返回 false。
         (2)若 r_i < root 则说明满足二叉搜索树定义,则继续遍历。

算法流程:
1、初始化: 单调栈 stack ,父节点值 root = +∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
2、倒序遍历 postorder :记每个节点为 r_i
        (1)判断: 若 r_i > root ,说明此后序遍历序列不满足二叉搜索树定义,直接返回 false;
        (2)更新父节点 root: 当栈不为空 且 r_i < stack.top() 时,循环执行出栈,并将出栈节点赋给 root。
        (3)入栈: 将当前节点 r_i 入栈;
3、若遍历完成,则说明后序遍历满足二叉搜索树定义,返回 true 。

翻转先序遍历又是root->right->left的,基于这样的性质和遍历方式,我们知道越往右越大,这样,就可以构造一个单调递增的栈,来记录遍历的元素。

为什么要用单调栈呢,因为往右子树遍历的过程,value是越来越大的,一旦出现了value小于栈顶元素value的时候,就表示要开始进入左子树了(如果不是,就应该继续进入右子树,否则不满足二叉搜索树的定义,不理解的请看下二叉搜索树的定义),但是这个左子树是从哪个节点开始的呢?

单调栈帮我们记录了这些节点,只要栈顶元素还比当前节点大,就表示还是右子树,要移除,因为我们要找到这个左孩子节点直接连接的父节点,也就是找到这个子树的根,只要栈顶元素还大于当前节点,就要一直弹出,直到栈顶元素小于节点,或者栈为空。栈顶的上一个元素就是子树节点的根。

接下来,数组继续往前遍历,之后的左子树的每个节点,都要比子树的根要小,才能满足二叉搜索树,否则就不是二叉搜索树。

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) 
    {
        // 单调栈使用,单调递增的单调栈
       stack<int> stk;
        int root = INT_MAX;
         // 逆向遍历,就是翻转的先序遍历
        for(int i = postorder.size() - 1 ; i >= 0 ; i--)
        {
            // 左子树元素必须要小于递增栈被top访问的元素,否则就不是二叉搜索树
            if(postorder[i] > root) return false;
            while(!stk.empty() && stk.top() > postorder[i])
            {
                // 数组元素小于单调栈的元素了,表示往左子树走了,记录下上个根节点
                // 找到这个左子树对应的根节点,之前右子树全部弹出,不再记录,因为不可能在往根节点的右子树走了
                root = stk.top();
                stk.pop();
            }
            // 这个新元素入栈
            stk.push(postorder[i]);
        }
        return true; 
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值