转自:https://blog.csdn.net/qq_27703417/article/details/55212859
题目:有一棵二叉树,其中所有节点的值都不一样,找到含有节点最多 的搜索二叉子树,并返回这棵子树的头节点.给定二叉树的头结点root,请返回所求的头结点,若出现多个节点最多的子树,返回头结点权值最大的。
思路:这道题目很好也很难,难点主要是对递归的考察,要好好理解,多次理解。之前的递归比较简单,例如单纯使用递归对二叉树进行先序、中序、后序遍历时每一次递归调用只是进行一些操作(将当前遍历的结点放入到list中),并不需要收集信息并不需要向上一级返回信息,因此很简单;之后在Tree9题目中每次递归调用时需要得到2个信息(该子树的最大长度,该子树的高度)并将这2个信息组成数组进行返回,因此每次递归调用时先接收上次调用返回的数组,然后进行处理,处理完的结果放入到一个新的数组中再返回这个数组,注意,Tree9中需要收集返回的2个信息都是int类型的,因此可以形成int[]数组进行返回;本题中由于业务逻辑更加复杂,每一次递归调用时需要利用的信息有4个,同时递归方法执行结束后返回的信息也有4个(当前子树的最大二叉搜索子树的根结点TreeNode,最大搜索子树的结点数目int,当前子树的最小值int,当前子树的最大值int)由于这4个信息不是同一类型的,因此不能组成一个数组进行返回(当然也可以组成一个map进行返回,map允许放入不同类型的数据),因此需要换种策略,其实思路什么的完全相同,只是实现策略上稍微有些变化而已。对于TreeNode的信息,依然可以直接返回,即令该递归函数的返回值是TreeNode,对于其他的3个int类型的参数,使用全局变量数组引用传递的方式来进行传递,即在调用递归方法时,除了传入根结点root之外,还要传入一个空的int[]数组,用来存放本方法处理后的结果,在方法的处理过程中,分别将计算得到的结果:最大搜索子树的结点数目、子树最小值、子树最大值分别放入到这个数组的tempRes[0],tempRes[1],tempRes[2]中,不需要显式地返回这个数组,只要将这个res[]定义为递归方法外的成员变量,那么在本方法对res[]数组进行赋值之后,这些值会自动保留,于是在本次递归方法执行结束返回后,在上一层递归方法中通过访问成员变量res[]就可以得到上一次递归方法执行后的结果。
TreeNodelnode=postOrder(root.left,res);
intlmax=res[0];
intlmin=res[1];
intlnum=res[2];
TreeNodernode= postOrder(root.right,res);
intrmax=res[0];
intrmin=res[1];
intrnum=res[2];
例如,上面的res数组可以是临时创建的,也可以是使用的成员变量数组,反正认为在TreeNode lnode=postOrder(root.left,res);调用结束后res就已经被赋值了,于是就可以取出res中的值进行使用了。Res数组可以重复使用,因为res传入给递归函数只是作为一个容器来让方法填充数据的。
本题的业务逻辑:
要求以root为根的二叉树中的最大搜索二叉子树的根结点,以root,root的左结点,root的右结点这个基础结构来分析怎么求二叉子树的最大搜索二叉树。
显然以root为根的二叉树的最大搜索子树只可能来自2中情况:
情况1:当root左子树root.left上的最大搜索子树就是left结点,并且root右子树上root.right上的最大搜索子树就是right结点时,并且left子树上的最大值<root.val<right子树上的最小值,此时root结点就是这棵树的最大搜索子树的根结点,返回root。
此时注意一种特殊情况,如果root的某一个子节点不存在,例如当左结点不存在时,如果上面的条件依旧满足,即right就是最大搜索子树,且root.val<right,那么此时的最大搜索子树就是right子树加上结点root;同理当right不存在,且left子树就是左结点的最大搜索子树并且left<root.val,那么此时的最大搜索子树就是left子树加上root结点,这种情况应该作为left,right都存在的特殊情况一起考虑进去,直接返回root结点,因此采取的巧妙的策略就是当root==null时,令该子树的结点数目为0,最小值为Integer.MAX_VALUE,于是显然大于root结点;令最小值为Integer.MIN_VALUE,于是显然小于root结点,并且返回null最为最大搜索子树的根结点。此时必然满足情况1的判断条件,于是会返回root作为搜索子树的根结点。
情况2:除此之外的情况都说明最大搜索子树只可能在root的左子树left或者右子树right上而不可能跨越root的两边组成一棵最大搜索树。并且已知左子树left上面搜索子树的根结点root1,其结点数目为n1;右子树right上面搜索子树的结点为right,其结点数为n2;因此比较n1与n2的大小即可,取较大者对应的根结点进行返回即可。
importjava.util.*;
/*给定根结点,求这棵二叉树中的最大搜索子树并返回根结点:
业务逻辑:最大搜索子树只可能来自2中情况
解决方案:递归:每层返回搜索树的根结点,收集搜索树的结点数目、子树最小值、子树最大值
*/
publicclass MaxSubtree {
public TreeNode getMax(TreeNode root) {
//特殊输入
if(root==null) return null;
//调用递归方法来得到最大搜索二叉子树的根结点
//该递归方法除了有返回值TreeNode外,还有3个int类型的数值信息
int res[]=new int[3];
TreeNodesearchNode=this.process(root,res);
//返回结果即可
return searchNode;
}
//写一个递归方法,传入根结点root,返回这个结点所在二叉树的最大搜索子树根结点
//根据后序遍历改编
private TreeNode process(TreeNoderoot,int[] res){
//边界条件
if(root==null){
//F1:对于空节点,最大搜索树结点是null,结点数是0,最小值是Integer.max,
//最大值是Integer.min(为了直接返回root)
res[0]=0;//搜索树的结点数目
res[1]=Integer.MAX_VALUE;//子树的最小值
res[2]=Integer.MIN_VALUE;//子树的最大值
return null;
}
//①F3:先判断左子树的搜索子树的根结点
int[] params1=new int[3];
TreeNodeleftNode=this.process(root.left,params1);
//假设调用的方法已经执行完毕,那么搜索树结点返回,同时3个信息已经赋值到了params1数组中
//②F3:判断右子树的搜索树根结点
int[] params2=new int[3];
TreeNoderightNode=this.process(root.right,params2);
//根据2种情况判断最大搜索树
if(leftNode==root.left&&rightNode==root.right&¶ms1[2]<root.val&¶ms2[1]>root.val){
//情况1满足:root即为最大搜索子树
//先收集递归方法需要的信息
res[0]=params1[0]+params2[0]+1;
//千万注意:存在一侧子树不存在的情况:
//F2:如果左子树不存在那么此时左子树的最小值为null的最小值,为MAX_VALUE,显然不对,应该返回root.val,即总的来说:总是应该返回左子树最小值与root.val两者的较小值
res[1]=Math.min(params1[1],root.val);
//F2:如果右子树不存在那么此时右子树的最大值为null的最大值,为MIN_VALUE,显然不对,应该返回root.val,即总结来说:总是应该返回右子树最大值与root.val两者的较大值
res[2]=Math.max(params2[2],root.val);
return root;
}
//根据情况2判断最大搜索子树:结点多的那个子树
//最大搜索子树的结点数目:2子树中的较大值
res[0]=Math.max(params1[0],params2[0]);
//F4:当前子树的所有结点中的最小值(不仅指搜索树的最小值):2子树与root这3个数的最小值
res[1]=Math.min(Math.min(params1[1],params2[1]),root.val);
//F4:同理,指的是这整棵树的最大值,2子树与root这3个数的最大值
res[2]=Math.max(Math.max(params1[2],params2[2]),root.val);
//返回结点数目多的搜索树的根结点作为最大搜索树根结点;小心,是返回leftNode:rightNode,
//不是返回root.left和root.right
returnparams1[0]>params2[0]?leftNode:rightNode;
}
}
F1:很重要,当root是空节点时,依然要设置它的子树的最小值和最大值,此时为了不影响null所在的根结点作为结果返回因此作兼容处理,将此时的子树最大值设为MIN_VALUE,于是当右子树为null时,依然满足右子树的最大值<root.val,于是可以直接让root作为根结点返回;同理令空子树的最小值为MAX_VALUE,于是当左子树为null时,依然满足左子树的最大值<root.val,于是可以直接让root作为根结点返回;
F2:与上面的配套,在处理完左右子树,为3个参数进行赋值时要注意,如果满足root.left,root.right分别是左右子树的最大搜索树且满足root.left<root<root.right时,存在一种可能性,即左侧或者右侧的子树为空,此时可能依然满足上面的等式,其实质是:MIN_VALUE<root<MAX_VALUE,但是注意:此时在返回新的二叉树的最小值和最大值时可能出错,如果左子树为空,那么新的树的最小值并不是左子树的最小值,因为此时左子树为null所以它的最小值是MAX_VALUE,如果右子树为空,那么新的树的最大值不是右子树的最大值,因为此时右子树为null,所以它的最大值为MIN_VALUE,这两种情况是由于上面的特殊处理造成的,而此时应该都返回root.val,于是在处理时总是令返回的新树的最小值为Math.min(leftMin,root.val);新树的最大值为Math.max(rightMax,root.val).
F3:注意,每次递归调用时需要为3个参数进行赋值,这3个参数使用数组的方式进行组合,可以在递归方法内部作为局部变量进行创建而不需要作为全局变量或者成员变量进行创建,可以理解为递归方法调用时就是返回执行完成的,不会无限制地展开,而是立即实现功能,立即收敛。
int[] params1=new int[3];
TreeNodeleftNode=this.process(root.left,params1);
int[] params2=new int[3];
TreeNoderightNode=this.process(root.right,params2);
if(leftNode==root.left&&rightNode==root.right&¶ms1[2]<root.val&¶ms2[1]>root.val){
res[0]=params1[0]+params2[0]+1;
res[1]=Math.min(params1[1],root.val);
res[2]=Math.max(params2[2],root.val);
return root;
}
可以认为创建的局部变量params1传入递归方法后方法立即处理完成,即不要把它当做一个递归方法,当做一个普通的可以顺序执行完成的方法即可,不要展开,于是process()执行过后,params1,params2就有值了,可以直接在本方法中进行访问。即递归方法在使用时不要将其展开来进行理解,当做普通非递归方法理解即可。
F4:这里又歧义,所谓的求子树的最小值和最大值是求新的整棵子树上的最小值还是新的子树里面搜索二叉子树的最小值?同理最大值是整棵子树的最大值还是里面搜索子树的最大值,两者实现的代码都通过了,但是我认为应该是整棵新的二叉树上的最小值和最大值。