一、什么是二叉树
1、结构描述
Class Node {
V value;
Node left;
Node right;
}
2、二叉树的先序、中序、后序遍历
先序:任何子树的处理顺序都是,先头节点、再左子树、然后右子树(中左右)
中序:任何子树的处理顺序都是,先左子树、再头节点、然后右子树(左中右)
后序:任何子树的处理顺序都是,先左子树、在右子树、然后头节点(左右中)
package class07;
public class Code01_RecursiveTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
/**
* 先序遍历方式打印
* @param head
*/
public static void pre(Node head) {
if (head == null) {
return;
}
System.out.println(head.value);
pre(head.left);
pre(head.right);
}
/**
* 中序遍历方式
* @param head
*/
public static void in(Node head) {
if (head == null) {
return;
}
in(head.left);
System.out.println(head.value);
in(head.right);
}
/**
* 后序遍历方式
* @param head
*/
public static void pos(Node head) {
if (head == null) {
return;
}
pos(head.left);
pos(head.right);
System.out.println(head.value);
}
/**
* 是不是发现上面三个函数结构一样,只是打印位置不同
* @param head
*/
public static void f(Node head) {
if (head == null) {
return;
}
// 把打印行为放在这儿就是先序打印
f(head.left);
// 把打印行为放在这儿就是中序打印
f(head.right);
// 把打印行为放在这儿就是后序打印
}
public static void main(String[] args) {
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
Node n6 = new Node(6);
Node n7 = new Node(7);
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
n3.left = n6;
n3.right = n7;
n6.left = null;
n6.right = null;
n7.left = null;
n7.right = null;
pre(n1);
System.out.println();
in(n1);
System.out.println();
pos(n1);
}
}
3、所以递归遍历的本质是递归序
上面的代码,f函数处理节点的时候一定会回到函数3次
(1)刚进来的时候
(2)去左树转了一圈之后,返回来了
(3)去右树转了一圈之后,返回来了
所以
先序:第一次到达的时候打印
中序:第二次到达的时候打印
后序:第三次到达的时候打印
说明,任何一个节点,都有机会到它左树转一圈回到它,收集一些信息。任何节点都可以向它右树转一圈回到它,收集一些信息。还能第三次回到它,把左右两个信息做整合。这是在树上做动态规划的基础。
为什么一个方法可以改出三种序列,只是打印方法位置不同?因为递归很强,递归可以让一个东西来到3次,所以可以随便加工先序、中序、后序。
4、递归方式实现二叉树的先序、中序、后序遍历(看上面的代码)
(1)理解递归序
(2)先序、中序、后序都可以在递归的基础上加工出来
(3)第一次到达一个节点就打印就是先序、第二次打印即中序、第三次打印即后序
5、非递归方式实现二叉树的先序、中序、后序遍历
(1)任何递归函数都可以改成非递归
(2)自己设计压栈来实现
规则:
先序遍历
1)弹出就打印
2)如果有右孩子,先压入右孩子,如果没有就不压
3)然后如果有左孩子,就压入左孩子,没有就不压了,先右后左
4)出栈顺序:头、左、右
5)出栈结果:1,2,4,5,3,6,7
中序遍历
1)整条左边界依次入栈
2)第一步到底了,弹出就打印
3)来到弹出节点的右树上,继续执行第一步
4)出栈结果:4,2,5,1,6,3,7
后续遍历
1)弹出就打印
2)基于先序遍历,对每一颗子树来说,先头,再右,再左。把右和左交换一下,改成先头,再左,再右
3)如有左,压入左
4)如有右,压入右
5)出栈顺序:头、右、左
6)此时出栈结果:1,3,7,6,2,5,4
7)把出栈结果逆着看就是后序:4,5,2,6,7,3,1
package class07;
import java.util.Stack;
public class Code02_UnRecursiveTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node (int v) {
value = v;
}
}
/**
* 用非递归方式实现先序遍历
* @param head
*/
public static void pre(Node head) {
System.out.print("pre-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
stack.add(head); //把头节点放进去
while (!stack.isEmpty()) {
head = stack.pop(); //弹出就打印
System.out.print(head.value + " ");
if (head.right != null) { //右孩子不为空,压右孩子
stack.push(head.right);
}
if (head.left != null) { //左孩子不为空,压左孩子
stack.push(head.left);
}
}
}
System.out.println();
}
/**
* 用非递归方式实现中序遍历
* @param head
*/
public static void in(Node head) {
System.out.print("in-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || head != null) { //栈不等于空或者头节点不等于空
if (head != null) {
stack.push(head); //压入头节点
head = head.left; //头节点往左窜
} else {
head = stack.pop();
System.out.print(head.value + " ");
head = head.right;
}
}
}
System.out.println();
}
/**
* 用非递归方式实现后序遍历
* @param head
*/
public static void pos1(Node head) {
System.out.print("pos-order: ");
if (head != null) {
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>(); //准备一个辅助栈
s1.push(head); //头节点进入到s1里去
while (!s1.isEmpty()) {
head = s1.pop(); //没一个节点从s1弹出了,不打印,加到s2里去
s2.push(head);
if (head.left != null) {
s1.push(head.left);
}
if (head.right != null) {
s1.push(head.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
/**
* 用一个栈搞定后序遍历
* @param h
*/
public static void pos2(Node h) {
System.out.print("pos-order: ");
if (h != null) {
Stack<Node> stack = new Stack<Node>();
stack.push(h); //头节点压入栈
Node c = null;
while (!stack.isEmpty()) {
c = stack.peek(); //c指向顶部节点,没有弹出
if (c.left != null && h != c.left && h != c.right) {
stack.push(c.left); //栈中压入c的左孩子
} else if (c.right != null && h != c.right) {
stack.push(c.right);
} else {
System.out.print(stack.pop().value + " ");
h = c;
}
}
}
System.out.println();
}
public static void main(String[] args) {
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
Node n6 = new Node(6);
Node n7 = new Node(7);
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
n3.left = n6;
n3.right = n7;
n6.left = null;
n6.right = null;
n7.left = null;
n7.right = null;
pre(n1);
in(n1);
pos1(n1);
pos2(n1);
}
}
6、实现二叉树的按层遍历
(1)按层级遍历二叉树
package class07;
import java.util.LinkedList;
import java.util.Queue;
public class Code03_LevelTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
public static void level(Node head) {
if (head == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head); //加入队列
while (!queue.isEmpty()) {
Node cur = queue.poll(); //弹出一个
System.out.println(cur.value); //打印
if (cur.left != null) { //先加左
queue.add(cur.left);
}
if (cur.right != null) { //再加右
queue.add(cur.right);
}
}
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
level(head);
}
}
(2)计算二叉树的最大宽度,其实就是宽度优先遍历,用队列
可以通过设置flag变量的方式,来发现某一层的结束
package class07;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
/**
* 求二叉树的最大宽度
*/
public class Code06_TreeMaxWidth {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 用map实现计算二叉树的宽度
* @param head
* @return
*/
public static int maxWidthUseMap(Node head) {
if (head == null) {
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
//value是Node,key是一个整数,表示key在哪一层
HashMap<Node, Integer> levelMap = new HashMap<>();
levelMap.put(head, 1);
int curLevel = 1; //当前在第几层
int curLevelNodes = 0; //当前层宽度目前是多少
int max = 0; //所有层的宽度的最大值
while (!queue.isEmpty()) {
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur); //当前节点在哪一层
if (cur.left != null) {
levelMap.put(cur.left, curNodeLevel + 1);
queue.add(cur.left);
}
if (cur.right != null) {
levelMap.put(cur.right, curNodeLevel + 1);
queue.add(cur.right);
}
//当前节点所在层数和目前统计的层数一样
if (curNodeLevel == curLevel) {
curLevelNodes++; //当前层的宽度加1
} else {
max = Math.max(max, curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
}
max = Math.max(max, curLevelNodes);
return max;
}
/**
* 不用map实现计算二叉树的宽度
* @param head
* @return
*/
public static int maxWidthNoMap(Node head) {
if (head == null) {
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
Node curEnd = head; //当前层,最右节点是谁
Node nextEnd = null; //如果有下一层,最右节点是谁
int max = 0;
int curLevelNodes = 0; //当前层的节点数
while (!queue.isEmpty()) {
Node cur = queue.poll(); //队列弹出一个节点
if (cur.left != null) {
queue.add(cur.left);
nextEnd = cur.left;
}
if (cur.right != null) {
queue.add(cur.right);
nextEnd = cur.right;
}
curLevelNodes++; //当前层加加
if (cur == curEnd) { //如果当前节点是当前层最右的节点
max = Math.max(max, curLevelNodes);
curLevelNodes = 0;
curEnd = nextEnd;
}
}
return max;
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
System.out.println(maxWidthUseMap(head));
System.out.println(maxWidthNoMap(head));
}
}
7、二叉树的序列化和反序列化
(1)可以用先序或者中序或者后序或者按层遍历,来实现二叉树的序列化
注意:不要忽略空节点,把这棵树用空节点补全
(2)用了什么方式序列化,就用什么方式反序列化
package class07;
import java.util.LinkedList;
import java.util.Queue;
/**
* 二叉树的序列化和反序列化
*/
public class Code04_SerializeAndReconstructTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 先序遍历的序列化
* @param head
* @return
*/
public static Queue<String> preSerial(Node head) {
Queue<String> ans = new LinkedList<>();
pres(head, ans);
return ans;
}
public static void pres(Node head, Queue<String> ans) {
if (head == null) {
ans.add(null);
} else {
ans.add(String.valueOf(head.value));
pres(head.left, ans);
pres(head.right, ans);
}
}
/**
* 先序遍历的反序列化
* @param prelist
* @return
*/
public static Node buildByPreQueue(Queue<String> prelist) {
if (prelist == null || prelist.size() == 0) {
return null;
}
return preb(prelist);
}
public static Node preb(Queue<String> prelist) {
String value = prelist.poll();
if (value == null) {
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = preb(prelist);
head.right = preb(prelist);
return head;
}
/**
* 按层序列化
* @param head
* @return
*/
public static Queue<String> levelSerial(Node head) {
Queue<String> ans = new LinkedList<>();
if (head == null) {
ans.add(null);
} else {
ans.add(String.valueOf(head.value)); //加入队列的时候,序列化进去
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while(!queue.isEmpty()) {
head = queue.poll();
if (head.left != null) { //左孩子不等于空,既序列化也加队列
ans.add(String.valueOf(head.left.value));
queue.add(head.left);
} else {
ans.add(null); //左孩子为空,只序列化不加队列
}
if (head.right != null) { //右孩子不等于空,既序列化也加队列
ans.add(String.valueOf(head.right.value));
queue.add(head.right);
} else {
ans.add(null); //右孩子为空,只序列化不加队列
}
}
}
return ans;
}
/**
* 按层反序列化
* @param head
* @return
*/
public static Node buildByLevelQueue(Queue<String> levelList) {
if (levelList == null || levelList.size() == 0) {
return null;
}
Node head = generateNode(levelList.poll());
Queue<Node> queue = new LinkedList<>();
if (head != null) {
queue.add(head);
}
Node node = null;
while (!queue.isEmpty()) {
node = queue.poll();
node.left = generateNode(levelList.poll()); //左孩子建出来,null是占位符
node.right = generateNode(levelList.poll()); //右孩子建出来,null是占位符
if (node.left != null) { //左孩子不等于空,队列加左孩子
queue.add(node.left);
}
if (node.right != null) { //右孩子不等于空,队列加右孩子
queue.add(node.right);
}
}
return head;
}
public static Node generateNode(String val) {
if (val == null) {
return null;
}
return new Node(Integer.valueOf(val));
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
Queue<String> queue = preSerial(head);
queue.forEach(str -> {
System.out.print(str + " ");
});
head = buildByPreQueue(queue);
System.out.println();
queue = levelSerial(head);
queue.forEach(str -> {
System.out.print(str + " ");
});
head = buildByLevelQueue(queue);
System.out.println();
queue = preSerial(head);
queue.forEach(str -> {
System.out.print(str + " ");
});
}
}
二、二叉树的递归套路
1、可以解决面试中绝大多数的二叉树问题,尤其是树型dp问题,本质是利用递归遍历二叉树的便利性
(1)假设以X节点为头,假设可以向X左树和X右树要任何信息
(2)在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要)
(3)列出所有可能性后,确定到底需要向左树和右树要什么样的信息
(4)把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S
(5)递归函数都返回S,每一棵子树都这么要求
(6)写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息
2、给定一棵二叉树的头节点head,返回这棵二叉树是不是平衡二叉树
平衡性:它的左子树和右子树的高度差的绝对值不超过1。
package class08;
/**
* 这棵二叉树是不是平衡二叉树
*/
public class Code01_IsBalanced {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 方法一
* @param head
* @return
*/
public static boolean isBalanced1(Node head) {
boolean[] ans = new boolean[1];
ans[0] = true;
process1(head, ans);
return ans[0];
}
public static int process1(Node head, boolean[] ans) {
if (!ans[0] || head == null) {
return -1;
}
int leftHeight = process1(head.left, ans);
int rightHeight = process1(head.right, ans);
if (Math.abs(leftHeight - rightHeight) > 1) {
ans[0] = false;
}
return Math.max(leftHeight, rightHeight) + 1;
}
/**
* 方法二
* @param head
* @return
*/
public static boolean isBalanced2(Node head) {
return process2(head).isBalanced;
}
/**
* 对左子树、右子树要求一样
* Info是信息返回的结构体
*/
public static class Info {
public boolean isBalanced;
public int height;
public Info(boolean b, int h) {
isBalanced = b;
height = h;
}
}
/**
* 以X为头的树
* @param X
* @return
*/
public static Info process2(Node X) {
if (X == null) {
return new Info(true, 0); //空树,返回是平衡树,深度是0
}
Info leftInfo = process2(X.left); //左树上是否平衡,左树上高度是多少
Info rightInfo = process2(X.right); //右树上是否平衡,右树上高度是多少
//X节点的高度,是左树的高度和右树的高度,取最大值,加当前头节点的高度
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
//看是否平衡
boolean isBalanced = true;
//左树不平衡,或者右树不平衡,或者左右树高度差大于1
if (!leftInfo.isBalanced || !rightInfo.isBalanced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
isBalanced = false;
}
return new Info(isBalanced, height);
}
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.6) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
Node head = generateRandomBST(100, 100);
System.out.println(isBalanced1(head));
System.out.println(isBalanced2(head));
}
}
3、给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离
分类:
(1)和X头节点无关
左树的最大距离,和右树的最大距离,取max
(2)和X头节点有关
最大距离通过X
X左树上离它最远的点,走到X右树上离它最远的点
即:左树的高度 + 1 + 右树的高度
package class08;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class Code08_MaxDistance {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 方法一(比较用)
* @param head
* @return
*/
public static int maxDistance1(Node head) {
if (head == null) {
return 0;
}
ArrayList<Node> arr = getPrelist(head);
HashMap<Node, Node> parentMap = getParentMap(head);
int max = 0;
for (int i = 0; i < arr.size(); i++) {
for (int j = i; j < arr.size(); j++) {
max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j)));
}
}
return max;
}
public static ArrayList<Node> getPrelist(Node head) {
ArrayList<Node> arr = new ArrayList<>();
fillPrelist(head, arr);
return arr;
}
public static void fillPrelist(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
arr.add(head);
fillPrelist(head.left, arr);
fillPrelist(head.right, arr);
}
public static HashMap<Node, Node> getParentMap(Node head) {
HashMap<Node, Node> map = new HashMap<>();
map.put(head, null);
fillParentMap(head, map);
return map;
}
public static void fillParentMap(Node head, HashMap<Node, Node> parentMap) {
if (head.left != null) {
parentMap.put(head.left, head);
fillParentMap(head.left, parentMap);
}
if (head.right != null) {
parentMap.put(head.right, head);
fillParentMap(head.right, parentMap);
}
}
public static int distance(HashMap<Node, Node> parentMap, Node o1, Node o2) {
HashSet<Node> o1Set = new HashSet<>();
Node cur = o1;
o1Set.add(cur);
while (parentMap.get(cur) != null) {
cur = parentMap.get(cur);
o1Set.add(cur);
}
cur = o2;
while (!o1Set.contains(cur)) {
cur = parentMap.get(cur);
}
Node lowestAncestor = cur;
cur = o1;
int distance1 = 1;
while (cur != lowestAncestor) {
cur = parentMap.get(cur);
distance1++;
}
cur = o2;
int distance2 = 1;
while (cur != lowestAncestor) {
cur = parentMap.get(cur);
distance2++;
}
return distance1 + distance2 - 1;
}
/**
* 方法二(用递归)
* @param head
* @return
*/
public static int maxDistance2(Node head) {
return process(head).maxDistance;
}
public static class Info {
public int maxDistance; //最大距离
public int height; //高度
public Info(int dis, int h) {
this.maxDistance = dis;
this.height = h;
}
}
public static Info process(Node X) {
if (X == null) {
return new Info(0, 0);
}
//左树给我的信息
Info leftInfo = process(X.left);
//右树给我的信息
Info rightInfo = process(X.right);
//加工高度
//左树和右树的最大高度,加上我自己
int height = Math.max(leftInfo.height , rightInfo.height) + 1;
//加工最大距离
//左树的最大距离、右树的最大距离、左树的高度+右树的高度+我自己,求最大值
int maxDistance = Math.max(Math.max(
leftInfo.maxDistance, rightInfo.maxDistance),
leftInfo.height + rightInfo.height + 1);
return new Info(maxDistance, height);
}
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.2) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
Node head = generateRandomBST(20, 100);
System.out.println(maxDistance1(head));
System.out.println(maxDistance2(head));
}
}
4、给定一棵二叉树的头节点head,返回这棵二叉树中最大的二叉搜索子树的头节点
什么叫搜索二叉树:整棵树上没有重复值,然后左树的值都比我小,右树的值都比我大,每棵子树都如此
部分是搜索二叉树:
分类:
(1)和X头节点无关
左树上满足搜索二叉树的子树或右树上的子树
(2)和X头节点有关
以X为头整体的树。左树整体是搜索二叉树,右树整体也是搜索二叉树,左树上的最大值要小于X,右树上的最小值要大于X
每个节点要收集的信息:
(1)最大二叉搜索子树的大小
(2)是否是搜索二叉树
(3)整棵树的最大值
(4)整棵树的最小值
package class08;
import java.util.ArrayList;
/**
* 给定一棵二叉树的头节点head,返回这棵二叉树中最大的二叉搜索子树的头节点
*/
public class Code04_MaxSubBSTSize {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static int getBSTSize(Node head) {
if (head == null) {
return 0;
}
ArrayList<Node> arr = new ArrayList<>();
in(head, arr);
for (int i = 1; i < arr.size(); i++) {
if (arr.get(i).value <= arr.get(i - 1).value) {
return 0;
}
}
return arr.size();
}
public static void in(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
in(head.left, arr);
arr.add(head);
in(head.right, arr);
}
public static int maxSubBSTSize1(Node head) {
if (head == null) {
return 0;
}
int h = getBSTSize(head);
if (h != 0) {
return h;
}
return Math.max(maxSubBSTSize1(head.left), maxSubBSTSize1(head.right));
}
public static int maxSubBSTSize2(Node head) {
if (head == null) {
return 0;
}
return process(head).maxSubBSTSize;
}
/**
* 任何子树都返回四个信息
*/
public static class Info {
public boolean isAllBST; //子树整体是不是BST
public int maxSubBSTSize; //满足搜索二叉树条件的最大子树的大小
public int min; //整棵树的最小值
public int max; //整棵树的最大值
public Info(boolean is, int size, int mi, int ma) {
isAllBST = is;
maxSubBSTSize = size;
min = mi;
max = ma;
}
}
public static Info process(Node X) {
if (X == null) { //如果是空树
return null;
}
Info leftInfo = process(X.left); //左树可以给我四个信息
Info rightInfo = process(X.right); //右树可以给我四个信息
//加工出我的四个信息
boolean isBST = false;
int min = X.value;
int max = X.value;
int maxSubBSTSize = 0;
if (leftInfo != null) { //如果左树不空
min = Math.min(min, leftInfo.min); //我的最小值,和左树信息中最小值比较
max = Math.max(max, leftInfo.max); //我的最大值,和左树信息中最大值比较
maxSubBSTSize = Math.max(maxSubBSTSize, leftInfo.maxSubBSTSize); //我的最大子树大小和左树信息中的最大子树大小比较
}
if (rightInfo != null) { //如果右树不空
min = Math.min(min, rightInfo.min); //我的最小值,和右树信息中最小值比较
max = Math.max(max, rightInfo.max); //我的最大值,和右树信息中最大值比较
maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize); //我的最大子树大小和右树信息中的最大子树大小比较
}
//是不是BST?
//X的值要大于左树的最大值,小于右树的最小值
// if ((leftInfo == null ? true : (leftInfo.isAllBST && leftInfo.max < X.value))
// && (rightInfo == null ? true : (rightInfo.isAllBST && rightInfo.min > X.value)))
if (
//更清晰的写法
(leftInfo == null ? true : leftInfo.isAllBST) //左树整体需要是搜索二叉树
&&
(rightInfo == null ? true : rightInfo.isAllBST) //右树整体需要是搜索二叉树
&&
(leftInfo == null ? true : leftInfo.max < X.value) //X的值要大于左树的最大值
&&
(rightInfo == null ? true : rightInfo.min > X.value) //X的值要小于右树的最小值
)
{
isBST = true;
//以X为头整体的树是搜索二叉树时,计算最大子树的大小,左树大小加右树大小加X这一个节点的大小
maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
}
return new Info(isBST, maxSubBSTSize, min, max);
}
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.2) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
Node head = generateRandomBST(20, 100);
System.out.println(maxSubBSTSize1(head)); //方法一,非常耗时
System.out.println(maxSubBSTSize2(head)); //方法二,递归,很快
}
}
5、派对的最大快乐值
员工信息的定义如下:
class Employee {
public int happy; //这名员工可以带来的快乐值
List<Employee> subordinates; //这名员工有哪些直接下级
}
公司的每个员工都符合Employee类的描述。整个公司的人员结构可以看作是一颗标准的、没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。
这个公司现在要办party,你可以决定哪些员工来,哪些员工不来,规则:
(1)如果某个员工来了,那么这个员工的所有直接下级都不能来
(2)派对的整体快乐值是所有到场员工快乐值的累加
(3)你的目标是让派对的整体快乐值尽量大
给定一棵多叉树的头节点boss,请返回派对的最大快乐值。
分类:
(1)X不来
X快乐值是0
(直接下级可以来,也可以不来)求下面每个节点来的时候整棵树的最大值,或者不来时整棵树的最大值,求max
(2)X来
X自己的快乐值
(直接下级肯定不来)加上下面每个节点不来时,整棵树的最大值
package class08;
import java.util.ArrayList;
import java.util.List;
public class Code09_MaxHappy {
public static class Employee {
public int happy;
public List<Employee> nexts;
public Employee(int h) {
happy = h;
nexts = new ArrayList<>();
}
}
public static int maxHappy1(Employee boss) {
if (boss == null) {
return 0;
}
return process1(boss, false);
}
public static int process1(Employee cur, boolean up) {
if (up) {
int ans = 0;
for (Employee next : cur.nexts) {
ans += process1(next, false);
}
return ans;
} else {
int p1 = cur.happy;
int p2 = 0;
for (Employee next : cur.nexts) {
p1 += process1(next, true);
p2 += process1(next, false);
}
return Math.max(p1, p2);
}
}
public static int maxHappy2(Employee boss) {
if (boss == null) {
return 0;
}
Info all = process2(boss);
return Math.max(all.yes, all.no);
}
public static class Info {
public int yes; //这个节点来的情况下整棵树的快乐最大值
public int no; //这个节点不来的情况下整棵树的快乐最大值
public Info(int y, int n) {
yes = y;
no = n;
}
}
public static Info process2(Employee x) {
if (x.nexts.isEmpty()) { //没有下级了,整棵树就你一个人
return new Info(x.happy, 0);
}
int yes = x.happy; //这个节点来
int no = 0; //这个节点不来
for (Employee next : x.nexts) {
Info nextInfo = process2(next); //对每一个下级都调递归函数要来它的信息
yes += nextInfo.no; //如果x是来的,则累加这个下级节点不来的子树的快乐最大值
no += Math.max(nextInfo.yes, nextInfo.no); //如果x是不来的,则每一个下级节点yes和no都做max累加到x的no上
}
return new Info(yes, no);
}
// for test
public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) {
if (Math.random() < 0.02) {
return null;
}
Employee boss = new Employee((int) (Math.random() * (maxHappy + 1)));
genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy);
return boss;
}
// for test
public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) {
if (level > maxLevel) {
return;
}
int nextsSize = (int) (Math.random() * (maxNexts + 1));
for (int i = 0; i < nextsSize; i++) {
Employee next = new Employee((int) (Math.random() * (maxHappy + 1)));
e.nexts.add(next);
genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy);
}
}
public static void main(String[] args) {
int maxLevel = 4;
int maxNexts = 7;
int maxHappy = 100;
int testTimes = 100000;
for (int i = 0; i < testTimes; i++) {
Employee boss = genarateBoss(maxLevel, maxNexts, maxHappy);
if (maxHappy1(boss) != maxHappy2(boss)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}