Leetcode刷题笔记——剑指 Offer 33. 二叉搜索树的后序遍历序列(中等)


题目描述

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

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

方法一:递归分治

  1. 终止条件: i ≥ j i≥j ij,说明此子树节点数量 ≤ 1 ≤1 1,无需判别正确性,直接返回true。
  2. 递归工作:
    1.划分左右子树:遍历后序遍历的 [ i , j ] [i,j] [i,j] 区间元素,寻找第一个大于根节点的节点,索引记为 m m m 。此时,可划分出左子树区间 [ i , m − 1 ] [i,m−1] [i,m1] 、右子树区间 [ m , j − 1 ] [m,j−1] [m,j1] 、根节点索引 j j j
    2.判断是否为二叉搜索树:
    左子树区间 [ i , m − 1 ] [i, m-1] [i,m1]内的所有节点都应 < p o s t o r d e r [ j ] < postorder[j] <postorder[j] 。而第步骤1已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
    右子树区间 [ m , j − 1 ] [m,j−1] [m,j1] 内的所有节点都应 > p o s t o r d e r [ j ] > postorder[j] >postorder[j] 。实现方式为遍历,当遇到 ≤ p o s t o r d e r [ j ] ≤postorder[j] postorder[j] 的节点则跳出;则可通过 p = j p=j p=j 判断是否为二叉搜索树。
  3. 返回值: 所有子树都需正确才可判定正确,因此使用与逻辑符 && 连接。

复杂度分析

时间复杂度 O ( N 2 ) O(N^2) O(N2) : 每次调用 r e c u r ( i , j ) recur(i,j) recur(i,j) 减去一个根节点,因此递归占用 O ( N ) O(N) O(N) ;最差情况下(即当树退化为链表),每轮递归都需遍历树所有节点,占用 O ( N ) O(N) O(N)

空间复杂度 O ( N ) O(N) O(N) : 最差情况下(即当树退化为链表),递归深度将达到 N N N

C++代码实现

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        return recur(postorder, 0, postorder.size()-1);
    }
    bool recur(vector<int>& postorder,int i, int j){
        if(i >= j)return true;
        int p = i;
        // 寻找第一个比根节点大的元素
        while(postorder[p] < postorder[j]) p++;
        // 标记该元素的序号
        int m = p;
        // 验证右子树区间元素是否大于根节点
        while(postorder[p] > postorder[j]) p++;
        return p == j && recur(postorder, i, m-1) && recur(postorder, m, j-1);
    }
};

方法二:辅助单调栈

  1. 后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。
    在这里插入图片描述

  2. 设后序遍历倒序列表为 [ r n , r n − 1 , . . . , r 1 ] [r_{n}, r_{n-1},...,r_1] [rn,rn1,...,r1],遍历此列表,设索引为 i i i ,若为二叉搜索树 ,则有:
    当节点值 r i > r i + 1 r_i > r_{i+1} ri>ri+1时: 节点 r i r_i ri 一定是节点 r i + 1 r_{i+1} ri+1的右子节点。
    当节点值 r i < r i + 1 r_i < r_{i+1} ri<ri+1时: 节点 r i r_i ri 一定是某节点 root 的左子节点,且 root 为节点 r i + 1 , r i + 2 , . . . , r n r_{i+1}, r_{i+2},..., r_{n} ri+1,ri+2,...,rn中值大于且最接近 r i r_i ri的节点(∵ root 直接连接左子节点 r i r_i ri)。

  3. 当遍历时遇到递减节点 r i < r i + 1 r_i < r_{i+1} ri<ri+1,若为二叉搜索树,则对于后序遍历中节点 r i r_i ri 右边的任意节点 r x ∈ [ r i − 1 , r i − 2 , . . . , r 1 ] r_x \in [r_{i-1}, r_{i-2}, ..., r_1] rx[ri1,ri2,...,r1],必有节点值 r x < r o o t r_x < root rx<root

注:节点 r x r_x rx 只可能为以下两种情况:① r x r_x rx r i r_i ri的左、右子树的各节点;② r x r_x rx r o o t root root 的父节点或更高层父节点的左子树的各节点。在二叉搜索树中,以上节点都应小于 r o o t root root
在这里插入图片描述
遍历 “后序遍历的倒序” 会多次遇到递减节点 r i r_i ri,若所有的递减节点 r i r_i ri 对应的父节点 r o o t root root 都满足以上条件,则可判定为二叉搜索树。
根据以上特点,考虑借助单调栈实现:

  1. 借助一个单调栈 stack 存储值递增的节点;
  2. 每当遇到值递减的节点 r i r_i ri,则通过出栈来更新节点 r i r_i ri 的父节点 r o o t root root
  3. 每轮判断 r i r_i ri r o o t root root的值关系:
    1. r i > r o o t r_i > root ri>root则说明不满足二叉搜索树定义,直接返回 f a l s e false false
    2. r i < r o o t r_i < root ri<root则说明满足二叉搜索树定义,则继续遍历。

算法流程:

  1. 初始化:单调栈 s t a c k stack stack,父节点值 r o o t = + ∞ root = +\infin root=+ 初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
  2. 倒序遍历 p o s t o r d e r postorder postorder :记每个节点为 r i r_i ri
    1. 判断: 若 r i > r o o t r_i>root ri>root,说明此后序遍历序列不满足二叉搜索树定义,直接返回 f a l s e false false
    2. 更新父节点 r o o t root root : 当栈不为空且 r i < s t a c k . p e e k ( ) r_i<stack.peek() ri<stack.peek()时,循环执行出栈,并将出栈节点赋给 r o o t root root
    3. 入栈:将当前节点 r i r_i ri入栈;
  3. 若遍历完成,则说明后序遍历满足二叉搜索树定义,返回 t r u e true true

复杂度分析

时间复杂度 O ( N ) O(N) O(N): 遍历 p o s t o r d e r postorder postorder 所有节点,各节点均入栈 / 出栈一次,使用 O ( N ) O(N) O(N) 时间。
空间复杂度 O ( N ) O(N) O(N): 最差情况下,单调栈 s t a c k stack stack 存储所有节点,使用 O ( N ) O(N) O(N) 额外空间。

C++代码实现

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        stack<int> stack;
        int root = INT_MAX;
        for(int i = postorder.size()-1; i >= 0; i--){
            if(postorder[i] > root) return false;
            while(!stack.empty() && stack.top() > postorder[i]){
                root = stack.top();
                stack.pop();
            }
            stack.push(postorder[i]);
        }
        return true;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于一棵的后,我们首先遍左子,然后遍右子,最后访问根节点。根据这个顺,我们可以借助一个栈来完成。 假设要输出根节点root到某一结点target的路径,首先,我们将root节点入栈。然后,进行以下循环: 1. 将栈顶节点弹出; 2. 如果栈顶节点的值等于target的值,则说明找到了目标结点,结束循环; 3. 如果栈顶节点的右子不为空且右子未被访问过,则将右子入栈; 4. 如果栈顶节点的左子不为空且左子未被访问过,则将左子入栈; 5. 如果栈顶节点的左右子都为空,说明该结点是叶子节点,将其出栈。 最终,栈保存的结点序列就是根节点到目标结点的路径。 下面是一个示例代码,假设的节点定义如下: ```python class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None ``` ```python def postorderPath(root, target): if root is None: return [] stack = [] result = [] visited = set() stack.append(root) while stack: node = stack[-1] if node in visited: stack.pop() result.pop() else: visited.add(node) result.append(node.val) if node.val == target: break if node.right and node.right not in visited: stack.append(node.right) if node.left and node.left not in visited: stack.append(node.left) return result ``` 这样,调用`postorderPath(root, target)`函数即可得到根节点root到目标结点target的路径。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卑微小岳在线debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值