树形DP
前提:如果题目求解目标是S规则,则求解流程可以定成以每一个节点为头节点的子树在S规则下的每一个答案,并且最终答案一定在其中。
二叉树节点间的最大距离问题
从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的节点个数叫作a到b的距离,那么二叉树任何两个节点之间都有距离,求整棵树上的最大距离。
分析:
- 当前节点X不参与的情况:当前最大距离可能来着左树的最大距离,右数的最大距离。
- 当前节点参与的情况下:左树最远节点,到右树最远节点。左树高 + 1(自己)+ 右树高。
- 三个值求最大值就为整棵树上的最大距离。对于每个节点需要2个信息:最大距离和树的高度。
1 | public class Morris { |
树形DP套路
- 以某个节点X为头节点的子树中,分析答案有哪些可能性,并且这种分析是以X的左子树、X的右子树和X整棵树的角度来考虑可能性。
- 根据第一步的可能性分析,列出所有需要的信息。
- 合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构
- 设计递归函数,递归函数是处理以X为头节点的情况下的答案。
包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把可能性做整合,并且要返回第三步的信息结构这四个小步骤
派对的最大快乐值
问题
公司的每个员工都符合Employee类的描述。整个公司的人员结构可以看作是一棵标准的、没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。
1 | class Employee { |
这个公司现在要办party,你可以决定哪些员工来,哪些员工不来。但是要遵循如下规则。
- 如果某个员工来了,那么这个员工的所有直接下级都不能来
- 派对的整体快乐值是所有到场员工快乐值的累加
- 你的目标是让派对的整体快乐值尽量大
- 给定一棵多叉树的头节点boss,请返回派对的最大快乐值。
解题
解题思路:当一棵树头节点为x时,如图所示,可分为两种情况:
①当x参与:则maxHappy = x(乐) + a树不来的快乐值 + b树b不来的快乐值
②当x不参与:则maxHappy = 0 + max(a树a来的快乐值, a树a不来的快乐值) + max(b树b来的快乐值, b树b不来的快乐值)
代码
1 | public class PartyMaxHappy { |
morris遍历
线索二叉树
morris
是二叉树遍历算法的进阶算法,morris
遍历可以将非递归遍历中的空间复杂度O(n)降为O(1)。从而实现时间复杂度为O(N),而空间复杂度为O(1)的精妙算法。
假设来到当前节点cur,开始时cur来到头节点位置
- 如果cur没有左孩子,cur向右移动
(cur = cur.right)
- 如果cur有左孩子,找到左子树上最右的节点
mostRight
:- 如果
mostRight
的右指针指向空,让其指向cur,
然后cur向左移动(cur = cur.left)
- 如果
mostRight
的右指针指向cur,让其指向null,
然后cur向右移动(cur = cur.right)
- 如果
3) cur为空时遍历停止

遍历结果为 1(左树最右为5), 2(左树最右为2), 4 (当来到4的时候左子树为空,向右移动)2, 5, 1, 3, 6, 3, 7
一些位置走了2遍,通过判断左树上最右节点的右指针是否指向自己,来判断是否已经遍历过自己。
代码
1 | public void mirrors(Node head) { |
对于每个节点虽然遍历了2编,但是所遍历的节点它们并不重复,固时间复杂度为O(n)
先中后序遍历
mirrors遍历的先序遍历,出现一次的直接打印,出现两次的只打印第一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public void mirrorsPre(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
// 找到左子树最右节点, 并且这个节点的右指针不指向自己
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
// 首次遍历
if (mostRight.right == null) {
// 出现2次的
System.out.println(cur.value);
mostRight.right = cur;
cur = cur.left;
continue;
} else { //mostRight.right == cur
mostRight.right = null;
}
}else{
// 出现一次的
System.out.println(cur.value);
}
cur = cur.right;
}
}中序遍历:出现一次的直接打印,出现两次的只打印第二次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public void mirrorsIn(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
// 找到左子树最右节点, 并且这个节点的右指针不指向自己
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
// 首次遍历
if (mostRight.right == null) {
// 出现2次的
mostRight.right = cur;
cur = cur.left;
continue;
} else { //mostRight.right == cur
mostRight.right = null;
}
}
System.out.println(cur.value);
cur = cur.right;
}
}后序遍历:只判断节点是不是第二次出现,若是逆序打印左树的右边界,整个过程之后,打印整颗子树的右边界。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public void mirrorsPost(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
// 找到左子树最右节点, 并且这个节点的右指针不指向自己
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
// 首次遍历
if (mostRight.right == null) {
// 出现2次的
mostRight.right = cur;
cur = cur.left;
continue;
} else { //mostRight.right == cur
mostRight.right = null;
printEdge(cur.left);
}
}
printEdge(cur.left);
cur = cur.right;
}
}
public void printEdge(Node x){
Node tail = reverseEdge(x);
Node cur = tail;
while (cur != null){
System.out.println(cur.value);
cur = cur.right;
}
reverseEdge(tail);
}
// 相当于链表反转
private Node reverseEdge(Node from) {
Node pre = null;
Node next = null;
while (from != null){
next = from.right;
from.right = pre;
pre = from;
from = next;
}
return pre;
}
判断一颗树是不是搜索二叉树
1 | public boolean isBST(Node head) { |