求二叉树中,包含的最大二叉搜索子树的节点数量是多少?
提示:本节仍然是重点说二叉树的DP递归套路,非常重要而且容易理解
二叉树的动态规划树形DP递归套路系列文章有这些,可以帮助你快速掌握树形DP的题目解题思想,就一个套路:
(1)判断二叉树是否为平衡二叉树?树形DP,树形动态规划的递归套路
(2)求二叉树中,任意两个节点之间的距离最大值是多少
题目
求二叉树中,包含的最大二叉搜索子树的节点数量是多少?
所谓二叉搜索树是指:
严格的遵守以下规则的二叉树:
(1)二叉树中没有重复值出现;
(2)任意节点x的左树中最大值严格小于x
(3)任意节点x的右树中最小值严格大于x
比如:
一、审题
示例:
可能一个二叉树head开头的树不见得是二叉搜索树,如果head开头的树整个是二叉搜索子树,那head就是最大的二叉搜索子树了
下面这个head就不是,它下面有一个子树是二叉搜索树,那就称这个子树为最大的二叉搜索子树,咱们需要统计这个子树有多少个节点数量?
下面这个树中,由于y开头的树,右树3<4,满足不了严格的搜索二叉树的条件,所以y不是搜索子树
显然h就不可能是,而x是最大的二叉搜索子树,节点数量为3
本题要用的二叉树为:
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
//构造一颗树,今后方便使用
public static Node generateBinaryTree() {
//树长啥样呢
// 1
// 2 3
// 4 5 6 7
// 8
// 9
Node head = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
head.left = n2;
head.right = n3;
Node n4 = new Node(4);
Node n5 = new Node(5);
n2.left = n4;
n2.right = n5;
Node n6 = new Node(6);
Node n7 = new Node(7);
n3.left = n6;
n3.right = n7;
Node n8 = new Node(8);
n4.left = n8;
Node n9 = new Node(9);
n8.left = n9;
return head;
}
//构造一颗树,今后方便使用
public static Node generateBinaryTree2() {
//树长啥样呢
// 4
// 2 5
// 1 3
Node head = new Node(4);
Node n2 = new Node(2);
Node n3 = new Node(5);
head.left = n2;
head.right = n3;
Node n4 = new Node(1);
Node n5 = new Node(3);
n2.left = n4;
n2.right = n5;
return head;
}
二、树形动态规划的递归套路:树形DP
95%的二叉树动态规划问题,都可以用以下套路解决:
一、定义个信息类:Info
收集x左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。
二、树形DP递归套路: 来到x节点
1)base case:考虑叶节点应该返回的信息Info
2)先收集左树和右树的信息Info
3)综合2)整理x自己的信息,并返回;
三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。
来,咱们举例说明:实践知真理!
三、解题思路:
一、定义个信息类:Info
收集x左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。
(1)本题,自然要返回一个信息:树是否是二叉搜索树?isBST【is Balanced Search Tree缩写】
(2)另外,我们要判断x开头的树,是否严格搜索?那就需要知道x的左树,它树上的最大值max,x的右树,它树上的最小值min;
这样我们才能判断maxL<x<minR是否成立?
光是这个成立还不行,咱还得确保,x左树它确实是搜索二叉树,x右树也是搜索二叉树,且同时满足maxL<x<minR条件,咱们才能断定x开头的树也是搜索二叉树
(3)如果确定了x开头的树是搜索二叉树,还需要统计x的节点数量,怎么统计呢?左树节点数+右树节点数+1(x自己算一个),这样作为x开头的数是二叉搜索树的节点数量。maxBSTSize——最大二叉搜索子树的节点数量。
注意哦,这个节点数量,我们示例中看过了,不一定非要是x开头的树哦,可能是x下面的某个子树。
无论如何,咱需要收集上面分析的(1)(2)(3)的信息,一共四个待确认值:
public static class Info {
public int max;//树上的最大值
public int min;//树上的最小值
public boolean isBST;//这个树,是否诶搜索二叉树
public int maxBSTSize;//最大二叉搜索树的节点数量--并不一定就是x开头的树
public Info(int maxv, int minv, boolean flag, int maxSize) {
max = maxv;
min = minv;
isBST = flag;
maxBSTSize = maxSize;
}
}
二、树形DP递归套路: 来到x节点,要收集节点x的信息:public static Info process(Node x)
1)base case:考虑叶节点应该返回的信息Info
本题中的这个基础情况,不太好整理,遇到了叶节点的null
咱不能确定这null的最小值和最大值,因为null没法跟Integer对比,所以这样子,咱就直接返回null
让下面3)重新初始化那四个信息,然后自己整合。if (head == null) return null;//叶节点暂时没法处理
2)先收集左树和右树的信息Info
这好说:
//两边获取信息
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
3)综合2)整理x自己的信息,并返回;
这里是本题的重点!
我们要整合出Info中的四个信息:
——min和max
咱们先初始化,min和max都是x自己,万一左树或者右树有更大,或者有更小的,只要左右不是null就更新
//整合出:max,min
int min = head.value;//先初始化一下,待会不一定会被覆盖的
int max = head.value;//得看与左子和右子比较的情况
if (leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);//最大最小都要比较的
}
if (rightInfo != null) {
max = Math.max(max, rightInfo.max);
min = Math.min(min, rightInfo.min);
}
//如果是叶节点,基本就是head自己的值
——整理isBST信息和maxBSTSize
当然了,h开头的最大二叉搜搜子树,可能与h无关:
即不是h开头的这个树,可能是h的左树,或者右树,
看看在谁那,比如下图,在h的右树x上,先更新给maxBSTSize再说:
//整合出maxBSTSize和isBST信息
int maxBSTSize = 0;
//这里如果是叶节点遇到了null,还得单独操作--有时候一棵树可能右子就为null
//null就是0个点
//这里要谨慎,如果x开头的树,压根没法形成BST的话,x的左树,或者右树,有最大二叉搜索树吗?
//有的话,先更新给我
if (leftInfo != null) maxBSTSize = Math.max(maxBSTSize, leftInfo.maxBSTSize);
if (rightInfo != null) maxBSTSize = Math.max(maxBSTSize, rightInfo.maxBSTSize);
另外,可能与h本身有关哦!
不妨设最大二叉搜索子树就是h开头的树,
得判断h开头的树是否为搜索二叉树?
判断条件比较牛:
判断之前,咱都得确认左树信息和右树信息是否存在?
如果左树或者右树信息是null,自然咱们认为它就是搜索二叉树!
如果左树或者右树信息不是null,则我们就看信息内放的isBST是不是true,保证左右同时都是搜索二叉树,与此同时
保证左树最大值<h<右树最小值【如果存在的话才比较】。
在这种情况下,x开头的树确实是最大二叉搜索树,那么更新maxBSTSize【如果左右树不是null的话,是null的话+0就行】
maxBSTSize为:左边的最大节点数+右边的最大节点数+1(h自己算一个);【这节点数包含整个二叉树呗】
boolean isBST = false;//初始化
//满足最大二叉搜索树的条件
if ((leftInfo == null ? true : leftInfo.isBST) &&
(rightInfo == null ? true : rightInfo.isBST) &&
(leftInfo == null ? true : leftInfo.max < head.value) &&
(rightInfo == null ? true : rightInfo.min > head.value)) {
//第一行条件,如果遇到叶节点则直接进来,因为叶节点null也是最大二叉搜索树
//如果信息不为空,就要看左树isBST了,它时,同时满足
//3和4的关系才行
isBST = true;
maxBSTSize = (leftInfo == null ? 0 : leftInfo.maxBSTSize) +
(rightInfo == null ? 0 : rightInfo.maxBSTSize) + 1;
//这里如果第一次遇到叶节点,则最大节点只会加1,在这加1一定不是null叶节点
//如果在这过程中,某一个子树没有点了,那不需要加,有才能加
}
仔细搞懂这个整合信息的步骤
细细品味,咱们二叉树动态规划树形DP的递归套路,是不是很经典
左右收集信息,然后整理出x节点的信息,返回:
下面手撕代码:收集节点x的信息
//复习
public static Info f(Node x){
//base case,null没法和Integer对比,返回null
if (x == null) return null;
//x的左右树收集信息
Info left = f(x.left);
Info right = f(x.right);
//整个四个信息
//——min和max
int min = x.value;
int max = x.value;//如果左右都没有,那就我自己了呗
if (left != null) {//左右树不null,需要更新
min = Math.min(min, left.min);
max = Math.max(max, left.max);
}
if (right != null) {//左右树不null,需要更新
min = Math.min(min, right.min);
max = Math.max(max, right.max);
}
//——整理isBST信息和maxBSTSize
//如果最大二叉搜索树与x无关,那先得确定一下左右树有没有?
int maxBSTSize = 0;//暂时默认为0
boolean isBST = false;//暂时就不是x
if (left != null) maxBSTSize = left.maxBSTSize;
if (right != null) maxBSTSize = Math.max(maxBSTSize, right.maxBSTSize);//左右谁有,更新谁
//默认最开始x不是最大二叉搜索树
//判断一下,可能x是吗,满足三个条件,全部齐全
boolean isLeftBST = left == null ? true : left.isBST;//null就是了
boolean isRightBST = right == null ? true : right.isBST;
boolean valueOK = (left == null ? true : left.max < x.value) &&
(right == null ? true : x.value < right.min);//就是maxL<x<minR
//仨都得满足,才能保证x就是二叉搜索树
if (isLeftBST && isRightBST && valueOK){
isBST = true;
//同时更新maxBSTSize
maxBSTSize = (left == null ? 0 : left.maxBSTSize) +
(right == null ? 0 : right.maxBSTSize) +
1;//算是x自己,左右节点数的和
}
return new Info(max, min, isBST, maxBSTSize);//顺序别搞反了
}
三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。
调用很简单,收集head的信息,反面里面的maxBSTSize
//调用
public static int reviewGetMaxBSTNum(Node head){
if (head == null) return 0;
Info info = f(head);
return info.maxBSTSize;
}
//树长啥样呢
// 1
// 2 3
// 4 5 6 7
// 8
// 9
//必然是1,结果最差就一个节点呗。
public static void test(){
Node cur = generateBinaryTree();
int max = getMaxBSTNum(cur);
int max2 = reviewGetMaxBSTNum(cur);
System.out.println(max);
System.out.println(max2);
}
//树长啥样呢
// 4
// 2 5
// 1 3
public static void test2(){
Node cur = generateBinaryTree2();
int max = getMaxBSTNum(cur);
int max2 = reviewGetMaxBSTNum(cur);
System.out.println(max);
System.out.println(max2);
}
public static void main(String[] args) {
test();
test2();
}
验证结果,如果x整个树都不是平衡搜索二叉树,就看其左右树了,
最次就是某个叶节点,它单独就是搜索二叉树,size=1(比如第1棵树)
如果确实x是整个搜索二叉树,那就返回x整个树上的节点数量(比如第二棵树)
1
1
5
5
再来看一个,不以头节点head开头的树为最大搜索二叉树,
而是在左树上有一个最大搜索二叉树
下面这个树,右边那个1违反规定了,重复出现,而且,它比4小,所以head不是BST
这个树的最大搜索二叉树就是2开头的子树,节点数量为3
//构造一颗树,今后方便使用
public static Node generateBinaryTree3() {
//树长啥样呢
// 4
// 2 1
// 1 3
//这个最大搜索二叉树就是2开头的子树,节点为3
Node head = new Node(4);
Node n2 = new Node(2);
Node n3 = new Node(1);//这里就违反了
head.left = n2;
head.right = n3;
Node n4 = new Node(1);
Node n5 = new Node(3);
n2.left = n4;
n2.right = n5;
return head;
}
public static void test3(){
Node cur = generateBinaryTree3();
int max = getMaxBSTNum(cur);
int max2 = reviewGetMaxBSTNum(cur);
System.out.println(max);
System.out.println(max2);
}
public static void main(String[] args) {
test();
test2();
test3();
}
3
3
果然,这个树形DP的递归套路很强大吧!
总结
提示:重要经验:
1)有时候,可能遇到叶节点左右为null时,我们要收集的最大值最小值不好确定,那就直接返回null信息,让整合信息时来确定x节点的信息。
2)树形DP的递归套路非常强大,这是可以解决绝大部分的二叉树动态规划问题的解题方法,一定要熟练使用。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。