题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
在这里小鸿为大家科普一下什么是二叉搜索树;
二叉搜索树,也叫二叉排序树、二叉查找树或BST(Binary Search Tree)。二叉搜索树或是一棵空疏,或者是一棵具有下列特性的非空二叉树:
1. 若左子树非空,则左子树上的所有节点的关键字值均小于根节点的关键字的值。 二叉搜索树;
2. 若右子树非空,则右子树上的所有节点的关键字值均大于根节点的关键字的值;
3. 左右子树,本身也是一棵二叉搜索树。
如下图二叉树,就是一个典型的二叉搜索树:
例如:输入数组{5,7,6,9,11,10,8},则返回true,因为这个整数序列是下图二叉搜索树的后序遍历序列。如果输入的数组是{7,4,6,5},则由于没有哪棵二叉搜索树的后序遍历结果是这个序列,因此返回false。
在后序遍历得到的序列中,最后一个数字是树的根节点的值。数组中前面的数字可以分为两部分:一部分是左子树节点的值,它们都比根节点的值小;一部分是右子树节点的值,它们都比根节点的值大。
以数组{5,7,6,9,11,10,8}为例,后序遍历结果的最后一个数字8就是根节点的值。在这个数组中,前面三个数字,5、6、7都比8小,所以是值为8的节点的左子树节点;后三个数字9、10、11都比8大,是值为8的节点的右子树节点。
接下来用同样的方法确定与数组每一部分对应的子树的结构。这其实就是一个递归过程。对于序列{5,7,6},最后一个数字6是左子树节点根节点的值。数字5比数字6小,所以5是值为6的节点的左子节点,而7则是它的右子节点。同理,对于序列{9,11,10},数字9比10小,所以值为10的节点的左子节点为9,而11则是它的右子节点。
那么再来分析上面提到的第二个数组{7,4,6,5}。后序遍历的最后一个数字是根节点,因此根节点的值是5 。由于第一个数字7大于5,因此在对应的二叉搜索树中,根节点上是没有左子树的,但是可以发现,在7后面有一个数字4小于根节点5,这是不符合二叉搜索树的性质的,所以不存在这样二叉搜索树使它的后序遍历序列为{7,4,6,5}。
得到以上规律之后,有以下参考代码:
using System;
namespace 二叉搜索树的后序遍历序列
{
class Program
{
static void Main(string[] args)
{
int[] arr1 = new int[] { 5, 7, 6, 9, 11, 10, 8 };
int[] arr2 = new int[] { 7, 4, 6, 5 };
Solution s = new Solution();
//Test1:数组为某个二叉树的后续遍历序列
Console.WriteLine("Test1:\n" + s.VerifySquenceOfBST(arr1));
//Test2:数组不是二叉树的后续遍历序列
Console.WriteLine("Test2:\n" + s.VerifySquenceOfBST(arr2));
//Test3:数组为空
Console.WriteLine("Test3:\n" + s.VerifySquenceOfBST(null));
}
}
class Solution
{
public bool VerifySquenceOfBST(int[] sequence)
{
//如果数组是空指针或者数组无元素,返回false
if (sequence == null || sequence.Length < 1)
return false;
//数组有元素,检查是否满足后序遍历序列的条件
return IsPostorder(sequence, 0, sequence.Length - 1);
}
/// <summary>
/// 用于检测数组是否为某个二叉树的后序遍历序列
/// </summary>
/// <param name="sequence">检测的数组</param>
/// <param name="start">数组开始的下标</param>
/// <param name="end">数组结束的下标</param>
/// <returns>如果是某个二叉树的后序遍历序列,返回true,否则返回false</returns>
private bool IsPostorder(int[] sequence, int start, int end)
{
//用于储存当前的根节点
int root = sequence[end];
//从start位置开始检索,直到遇到比根节点大的节点,在此节点之前的所有节点为该根节点的左子树
int i = start;
for (; i < end - 1; i++)
{
if (sequence[i] > root)
break;
}
//从i(比根节点大的第一个数字)开始检索,是否存在小于根节点的节点,若果有,说明不是搜索二叉树
int j = i;
for (; j < end - 1; j++)
{
if (sequence[j] < root)
return false;
}
//检查该节点的左子树是否为搜索二叉树
bool left = true;
if (i > 0)
left = IsPostorder(sequence, 0, i - 1);
//检查该节点的右子树是否为搜索二叉树
bool right = true;
if (i < end - 1)
right = IsPostorder(sequence, i, end - 1);
//如果该节点的左右子树都是搜索二叉树,返回true,否则返回false
return left && right;
}
}
}
总结:如果面试题要求处理一颗二叉树的遍历序列,则可以先找到二叉树的根节点,再基于根节点把整棵树的遍历序列拆分成左右子树对应的序列,接下来再递归处理这两个子序列。本题和另一个二叉树的题目十分相像“重建二叉树”,都是运用的这种思路。