333. 最大 BST 子树 - 力扣(LeetCode)
一、题目
给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,其中最大指的是子树节点数最多的。
注意:
子树必须包含其所有后代。
示例 :
示例:
输入: [10,5,15,1,8,null,7]
10
/ \
5 15
/ \ \
1 8 7
输出: 3
解释: 高亮部分为最大的 BST 子树。
返回值 3 在这个样例中为子树大小。
二、代码
// 提交如下的代码,可以直接通过
public static int largestBSTSubtree(TreeNode head) {
// 如果一棵树为空树,则其最大二叉搜索子树就是0
if (head == null) {
return 0;
}
// 通过二叉树递归求解
return process(head).maxBSTSubtreeSize;
}
// 信息类
public static class Info {
// 当前树的最大二叉搜索子树大小
public int maxBSTSubtreeSize;
// 当前树的大小
public int allSize;
// 当前树的最大值
public int max;
// 当前树的最小值
public int min;
public Info(int m, int a, int ma, int mi) {
maxBSTSubtreeSize = m;
allSize = a;
max = ma;
min = mi;
}
}
// 递归
public static Info process(TreeNode x) {
// 这里如果是空的话,我们不好去设置空树的max和min值,因为可以无穷大或无穷小。所以这里我们就直接返回null,让上层去做处理
if (x == null) {
return null;
}
// 左右子树递归去得到相应的Info
Info leftInfo = process(x.left);
Info rightInfo = process(x.right);
// 设置max、min、allSize初始值。
int max = x.val;
int min = x.val;
int allSize = 1;
// 在下面的过程中完成对null的处理
// 如果左子树不为空,则取更新max、min、allSize的值
if (leftInfo != null) {
// 与左子树的最大值比较
max = Math.max(leftInfo.max, max);
// 与左子树的最小值比较
min = Math.min(leftInfo.min, min);
// 节点数累加左子树的节点数
allSize += leftInfo.allSize;
}
// 右子树同理
if (rightInfo != null) {
max = Math.max(rightInfo.max, max);
min = Math.min(rightInfo.min, min);
allSize += rightInfo.allSize;
}
// 到这里,就将最大值,最小值,节点数三个数据都求出来了,下面再去求最大搜索二叉子树大小
// 下面这是考虑三种情况,1、最大二叉搜索树在左子树中,2、最大二叉搜索树在右子树中,3、最大二叉搜索树就是其本身
// 设置情况1的最大二叉搜索子树大小初始值
int p1 = -1;
// 直接获取左子树的最大二叉搜索子树大小
if (leftInfo != null) {
p1 = leftInfo.maxBSTSubtreeSize;
}
// 设置情况2的最大二叉搜索子树大小初始值
int p2 = -1;
// 直接获取右子树的最大二叉搜索子树大小
if (rightInfo != null) {
p2 = rightInfo.maxBSTSubtreeSize;
}
// 设置情况3的最大二叉搜索子树大小初始值
int p3 = -1;
// 这个情况的前提是当前树必须是一棵二叉搜索出才可以。所以先去判断这棵树是否是一颗二叉搜索树
// 判断左子树是否是二叉搜索树 通过最大二叉搜索树大小是否等于左子树节点数判断
boolean leftBST = leftInfo == null ? true : (leftInfo.maxBSTSubtreeSize == leftInfo.allSize);
// 判断右子树是否是二叉搜索树
boolean rightBST = rightInfo == null ? true : (rightInfo.maxBSTSubtreeSize == rightInfo.allSize);
// 如果左右子树都是二叉搜索树,就在去判断x是否大于左子树最大值,是否小于右子树最小值,只要这些条件都符合,就说明这是一棵二叉搜索树
if (leftBST && rightBST) {
// x是否大于左子树最大值
boolean leftMaxLessX = leftInfo == null ? true : (leftInfo.max < x.val);
// x是否小于右子树最小值
boolean rightMinMoreX = rightInfo == null ? true : (x.val < rightInfo.min);
// 如果上述条件都成立,说明当前树是一颗搜索二叉树
if (leftMaxLessX && rightMinMoreX) {
// 下面就去计算当前二叉树的最大二叉搜索子树大小,也就是计算当前二叉树的节点个数
// 这里仍然要记得判断空树
int leftSize = leftInfo == null ? 0 : leftInfo.allSize;
int rightSize = rightInfo == null ? 0 : rightInfo.allSize;
// 左右子树节点数加和 + 1
p3 = leftSize + rightSize + 1;
}
}
// 最后比较p1、p2、p3大小,找出最大的一个就是当前树的最大二叉搜索树的大小,再把其他的相关信息向上返回
return new Info(Math.max(p1, Math.max(p2, p3)), allSize, max, min);
}
三、解题思路?
1、首先根据题意,找到目标。这道题就是找到以X为根结点的二叉树中最大的二叉搜索子树的大小。
2、根据我们的目标,分析所有的可能性。
- 和X无关,即X并不是一颗二叉搜索树,也就是说以X为根的树的最大二叉搜索子树在其左右子树中,并不以X节点为头。这种情况下有两种可能:
- 以X为根节点的树的最大二叉搜索子树在其左子树中,也就是说以X为根节点的树的最大二叉搜索子树大小就是其左子树的最大二叉搜索子树大小。
- 以X为根节点的树的最大二叉搜索子树在其右子树中,也就是说以X为根节点的树的最大二叉搜索子树大小就是其右子树的最大二叉搜索子树大小。
- 和X有关,即X是一颗二叉搜索树,也就是说以X为根的树的最大二叉搜索子树就是其本身,以X节点为头。所以最大的二叉搜索子树的大小就是以X为根节点的二叉树的节点数。这种情况,只有当以X为根节点的二叉树是一颗搜索二叉树才会成立,那么什么情况下它才能是搜索二叉树呢,下面我们列出所需要的所有条件:
- X树的左子树必须是搜索二叉树,X的右子树必须是搜索二叉树
- X要大于X左子树的max值,X要小于X右子树的min值
满足这两个条件我们就能确定X就是一棵搜索二叉树了,那么如果想要求X最大二叉搜索子树的大小,就是求X树本身的大小,那么就需要知道X的左右子树的大小才可以。
3、依照2找出来的所有可能性,我们就能知道想要得到最后的大小结果,到底需要左右子树提供给我们哪些信息,根据这些信息来设计我们的递归传递的信息类Info。
根据可能1),如果想要知道最大二叉搜索子树大小,就需要知道其左右子树的最大二叉搜索子树的大小,所以我们需要每一个树的最大二叉搜索子树大小。
根据可能2),我们需要先判断X树是不是二叉搜索树,需要的信息就有,左右子树是否为搜索二叉树,左子树的max值和右子树的min值,这里因为左右子树要求的树不同(一个max,一个min),所以直接求全集。所以就需要每棵树是否为二叉搜索树,每棵树的最大值max和最小值min。
然后可能2)判断了X是二叉搜索子树之后,如果想得到最大二叉搜索子树的大小,就需要知道X树的大小,想知道X树的大小,就需要通过其左右子树的大小和 + 1得到,那么就需要其左右子树的大小,所以我们还需要每棵树的大小。
综上所述,我们需要在Info中存储的信息有每一个树的最大二叉搜索子树大小、每棵树是否为二叉搜索树、每棵树的最大值max和最小值min、每棵树的大小。并且我们可以对Info做一些简化,原则就是看哪些信息可以通过其他的信息推导出来,这样的就可以省略掉。当一棵树是二叉搜索树时,那么其最大二叉搜索子树大小就等于它本身的大小,所以每棵树是否为二叉搜索树就可以通过每棵树的最大二叉搜索子树大小和每棵树大小是否相等来推导出来,每棵树是否为二叉搜索树这个信息就可以省略掉。
最终,我们就需要向Info中存储每一个树的最大二叉搜索子树大小、每棵树的最大值max和最小值min、每棵树的大小 这四个信息。
4、利用左右子树传递上来的数据,去计算当前树的这些数据。知道了我们要在每一层递回向上返回哪些数据,剩下的要做的就是用代码去计算出这些数据的值,生成Info对象即可。就根据每一个值得特性,利用其左右子树传递上来的数据,来计算出当前树的这四个数据。
|