剑指offer | 面试题26:二叉搜索树的后序遍历序列_后序遍历

不知不觉已经100篇原创了


欢迎关注千羽的公众号剑指offer | 面试题26:二叉搜索树的后序遍历序列_剑指offer_02程序员千羽

Leetcode : https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof


GitHub :  https://github.com/nateshao/leetcode/blob/main/algo-notes/src/main/java/com/nateshao/sword_offer/topic_26_verifyPostorder/Solution.java


剑指 Offer 26. 二叉搜索树的后序遍历序列

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

参考以下这颗二叉搜索树:

5
/ \
2 6
/ \
1 3

示例 1:

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

示例 2:

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


解题思路:

  • 后序遍历定义:​[ 左子树 | 右子树 | 根节点 ]​​ ,即遍历顺序为 “左、右、根” 。
  • 二叉搜索树定义:

  • 左子树中所有节点的值 < 根节点的值;
  • 右子树中所有节点的值 > 根节点的值;
  • 其左、右子树也分别为二叉搜索树。

剑指offer | 面试题26:二叉搜索树的后序遍历序列_后序遍历_03

方法一:递归

  • 根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
递归解析:
  • 终止条件: 当 i ≥ j,说明此子树节点数量 ≤ 1 ,无需判别正确性,因此直接返回 true ;
  • 递推工作:

  • 左子树区间[i, m- 1]内的所有节点都应< postorder[j]。而第1. 划分左右子树步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
  • 右子树区间[m,j- 1]内的所有节点都应> postorder[j] 。实现方式为遍历,当遇到≤ postorder[j] 的节点则跳出;则可通过 p = j 判断是否为二叉搜索树。


  1. 划分左右子树:遍历后序遍历的[i,j] 区间元素,寻找第一个大于根节点的节点,索引记为m 。此时,可划分出左子树区间[i,m- 1]、 右子树区间[m,j- 1]、 根节点索引 j。
  2. 判断是否为二叉搜索树:

  • 返回值:所有子树都需正确才可判定正确,因此使用 与逻辑符 &&连接。

  1. p=j :判断此树否正确。
  2. recur(i,m- 1) :判断 此树的左子树 否正确。
  3. recur(m,j-1) :判断 此树的右子树 是否正确。

剑指offer | 面试题26:二叉搜索树的后序遍历序列_后序遍历_04

复杂度分析:


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

package com.nateshao.sword_offer.topic_26_verifyPostorder;

import java.util.Stack;

/**
* @date Created by 邵桐杰 on 2021/11/30 14:22
* @微信公众号 程序员千羽
* @个人网站 www.nateshao.cn
* @博客 https://nateshao.gitee.io
* @GitHub https://github.com/nateshao
* @Gitee https://gitee.com/nateshao
* Description:
*/
public class Solution {
public static void main(String[] args) {
int[] postorder = {1, 3, 2, 6, 5};
int[] postorder2 = {1, 6, 3, 2, 5};// false
int[] postorder3 = {1, 6, 3, 2, 5};// false
System.out.println("递归:(postorder) = " + verifyPostorder(postorder));
System.out.println("递归:(postorder2) = " + verifyPostorder(postorder2));
}

public static boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}

public static boolean recur(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);
}
/**
* 递归:(postorder) = true
* 递归:(postorder2) = false
*/
}

方法二:栈

后序遍历 倒序:​[ 根节点 | 右子树 | 左子树 ]​​ 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。

剑指offer | 面试题26:二叉搜索树的后序遍历序列_剑指offer_05剑指offer | 面试题26:二叉搜索树的后序遍历序列_子树_06

算法流程:

  1. 初始化: 单调栈stack,父节点值root = +∞(初始值为正无穷大, 可把树的根节点看为此无穷大节点的左孩子) ;
  2. 倒序遍历postorder: 记每个节点为ri;

  1. 判断: 若ri > root,说明此后序遍历序列不满足一 叉搜索树定义,直接返回false ;
  2. 更新父节点root: 当栈不为空 且ri < stack:peek()时,循环执行出栈,并将出栈节点赋给root
  3. 入栈: 将当前节点ri入栈;

  1. 若遍历完成,则说明后序遍历满足二叉 搜索树定义,返回true。
package com.nateshao.sword_offer.topic_26_verifyPostorder;

import java.util.Stack;

/**
* @date Created by 邵桐杰 on 2021/11/30 14:22
* @微信公众号 程序员千羽
* @个人网站 www.nateshao.cn
* @博客 https://nateshao.gitee.io
* @GitHub https://github.com/nateshao
* @Gitee https://gitee.com/nateshao
* Description: 二叉搜索树的后序遍历序列
*/
public class Solution {
public static void main(String[] args) {
int[] postorder3 = {1, 6, 3, 2, 5};// false
System.out.println("栈:(postorder3) = " + verifyPostorder2(postorder3));
}

/**
* 方法二:栈
*
* @param postorder
* @return
*/
public static boolean verifyPostorder2(int[] postorder) {
Stack<Integer> stack = new Stack<>();
//初始化: 单调栈 stackstack ,父节点值 root = +∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
int root = Integer.MAX_VALUE;
// 倒序遍历 postorder:记每个节点为 i
for (int i = postorder.length - 1; i >= 0; i--) {
// 若 ri>root,说明此后序遍历序列不满足二叉搜索树定义,直接返回 false ;
if (postorder[i] > root) return false;
// 更新父节点root:当栈不为空且 stack.peek()>ri时,循环执行出栈,并将出栈节点赋给 root
while (!stack.isEmpty() && stack.peek() > postorder[i])
root = stack.pop();
stack.add(postorder[i]); // 入栈: 将当前节点 ri
}
return true; //遍历完成,则说明后序遍历满足二叉搜索树定义,返回 true 。
}
/**
* 递归:(postorder) = true
* 递归:(postorder2) = false
* 栈:(postorder3) = false
*/
}

剑指offer | 面试题26:二叉搜索树的后序遍历序列_后序遍历_07

参考链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/mian-shi-ti-33-er-cha-sou-suo-shu-de-hou-xu-bian-6

剑指offer | 面试题26:二叉搜索树的后序遍历序列_子树_08

END


革命尚未成功,同志仍需努力,冲冲冲