目 录:
最近看了左神的高阶算法视频,觉得很多算法题目解题思路确实是非常套路化的。本文整理了左神算法讲解中关于二叉树信息收集问题的高度套路。都是首先想好假设,想和子树要什么样的信息,然后自己拿到这个信息需要返回自己的什么信息。
高度套路化:列出可能性 -> 从子过程收集的信息中整合出本过程要返回的信息 -> 返回。
1、舞会的最大活跃度
题目:一个公司的上下级关系是一棵多叉树,这个公司要举办晚会,你作为组织者已经摸清了大家的心理:一个员工的直接上级如果到场,这个员工肯定不会来。每个员工都有一个活跃度的值(值越大,晚会上越活跃),你可以给某个员工发邀请函以决定谁来,怎么让舞会的气氛最活跃?返回最大的活跃值。
- 举例:
如果邀请 A 来,那么其直接下属 BCD 一定不会来,你可以邀请 EFGHJKL 中的任意几个来,如果都邀请,那么舞会最大活跃度为 A(2)+E(9)+F(11)+G(2)+H(4)+J(7)+K(13)+L(5)
;但如果选择不邀请 A 来,那么你可以邀请其直接下属 BCD 中任意几个来,比如邀请 B 而不邀请 CD,那么 B 的直接下属 E 一定不回来,但 CD 的直接下属你可以选择性邀请。
大前提:如果你知道每个员工来舞会或不来舞会对舞会活跃值的影响,那么舞会最大活跃值就容易得知了。比如是否邀请 A 来取决于:B 来或不来两种情况中选择对舞会活跃值增益最大的那个 +C 来或不来两种情况中选择对舞会活跃值增益最大的那个 +D 来或不来两种情况中选择对舞会活跃值增益最大的那个;同理,对于任意一名员工,是否邀请他来都是用此种决策。
列出可能性:来或不来。
子过程要收集的信息:返回子员工来对舞会活跃值的增益值和不来对舞会的增益值中的较大值。
public class MaxHappyInDanceMeeting {
public static class Node{
int happy;
List<Node> subs;
public Node(int happy){
this.happy = happy;
this.subs = new ArrayList<>();
}
}
public static class ReturnData{
int maxHappy;
public ReturnData(int maxHappy){
this.maxHappy = maxHappy;
}
}
public static ReturnData process(Node node){
if(node.subs.size() == 0){
// 如果没有子节点了,就直接返回自己的活跃度
return new ReturnData(node.happy);
}
// case1:node 去
int go_Happy = node.happy;
// case2:ndoe 不去
int unGo_Happy = 0;
for(Node sub : node.subs){
unGo_Happy += process(sub).maxHappy;
}
return new ReturnData(Math.max(go_Happy, unGo_Happy));
}
public static int maxPartyHappy(Node root){
if(root == null){
return 0;
}
return process(root).maxHappy;
}
}
2、求一棵二叉树的最大搜索二叉子树的结点个数
这类题一般都有一个大前提:假设对于以树中的任意结点为头结点的子树,我们都能求得其最大搜索二叉子树的结点个数,那么答案一定就在其中。
而对于以任意结点为头结点的子树,其最大搜索二叉子树的求解分为三种情况(列出可能性):
- 整棵树的最大搜索二叉子树存在于左子树中。这要求其左子树中存在最大搜索二叉子树,而其右子树不存在。
- 整棵树的最大搜索二叉子树存在于右子树中。这要求其右子树中存在最大搜索二叉子树,而其左子树不存在。
- 最整棵二叉树的最大搜索二叉子树就是其本身。这需要其左子树就是一棵搜索二叉子树且左子树的最大值结点比头结点小、其右子树就是一棵搜索二叉子树且右子树的最小值结点比头结点大。
要想区分这三种情况,我们需要收集的信息:
- 子树中最大搜索二叉树的大小
- 子树的头结点
- 子树的最大值结点
- 子树的最小值结点
因此我们就可以开始我们的高度套路了:
1、 将要从子树收集的信息封装成一个 ReturnData
,代表处理完这一棵子树要向上级返回的信息。
2、假设我利用子过程收集到了子树的信息,接下来根据子树的信息和分析问题时列出的情况加工出当前这棵树要为上级提供的所有信息,并返回给上级(整合信息)。
3、确定 baseCase
,子过程到子树为空时,停。
根据上面高度套路的分析,可以写出解决这类问题高度相似的代码:
public class FIndMaxSizeBST {
public static class Node{
public int data;
public Node left;
public Node right;
public Node(int data){
this.data = data;
}
}
// 构造返回信息
public static class ReturnData{
public int size; // 最大二叉搜索子树的大小
public Node head; // 子树的头结点
public int max; // 子树中的最大结点值
public int min; // 子树中的最小结点值
public ReturnData(int size, Node head, int max, int min){
this.size = size;
this.head = head;
this.max = max;
this.min = min;
}
}
public static ReturnData process(Node root){
if(root == null){
return new ReturnData(0, null, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
ReturnData leftInfo = process(root.left);
ReturnData rightInfo = process(root.right);
int leftSize = leftInfo.size; // case1
int rightSize = rightInfo.size; // case2
int selfSize = 0;
if(leftInfo.head == root.left && rightInfo.head == root.right && leftInfo.max < root.data && rightInfo.min > root.data){
selfSize = leftSize + rightSize + 1; // case3
}
int maxSize = Math.max(Math.max(leftSize, rightSize), selfSize);
Node maxHead = leftSize > rightSize ? leftInfo.head : selfSize > rightSize ? root : rightInfo.head;
return new ReturnData(maxSize, maxHead,
Math.max(Math.max(leftInfo.max, rightInfo.max), root.data),
Math.min(Math.min(leftInfo.min, rightInfo.min), root.data));
}
}
3、求一棵二叉树的最远距离
题目:如果在二叉树中,小明从结点 A 出发,既可以往上走到达它的父结点,又可以往下走到达它的子结点,那么小明从结点 A 走到结点 B 最少要经过的结点个数(包括 A 和 B)叫做 A 到 B 的距离,任意两结点所形成的距离中,最大的叫做树的最大距离。
高度套路化:
大前提:如果对于以该树的任意结点作为头结点的子树中,如果我们能够求得所有这些子树的最大距离,那么答案就在其中。
对于该树的任意子树,其最大距离的求解分为以下三种情况:
- 该树的最大距离是左子树的最大距离(最大距离的两个节点都在左子树中)。
- 该树的最大距离是右子树的最大距离(最大距离的两个节点都在右子树中)。
- 该树的最大距离是从左子树的最深的那个结点经过该树的头结点走到右子树的最深的那个结点。
要从子树收集的信息:
- 子树的最大距离
- 子树的深度
public class FIndMaxDisInTree {
public class Node{
public int val;
public Node left;
public Node right;
public Node(int val){
this.val = val;
}
}
public static class ReturnData{
public int maxDis; // 最大距离
public int height; // 最大高度
public ReturnData(int maxDis, int height){
this.maxDis = maxDis;
this.height = height;
}
}
public static int getMaxDis(Node root){
return process(root).maxDis;
}
// 时间复杂度都是O(N),其实就是二叉树的后序遍历:先找左树要信息,再找右树要信息,最后给出自己的信息
public static ReturnData process(Node node){
if(node == null){
return new ReturnData(0, 0);
}
ReturnData leftData = process(node.left);
ReturnData rightData = process(node.right);
int maxDis = Math.max(leftData.maxDis, rightData.maxDis);
int height = Math.max(leftData.height, rightData.height) + 1;
// 下面这步要用到高度信息,所以必须放在高度信息收集的后面
maxDis = Math.max(maxDis, leftData.height + rightData.height + 1);
return new ReturnData(maxDis, height);
}
}
参考:https://www.nowcoder.com/discuss/150061?type=0&order=0&pos=9&page=1