树形dp套路
使用前提:
如果题目求解目标是S规则,则求解流程可以定成以每个节点为头结点的子树在S规则下的每一个答案,并且最终答案一定在其中
套路讲解
树形dp套路第一步:
以某个节点x为头结点的子树中,分析答案有哪些可能性,并且这种分析是以x的左子树,x的右子树和x整棵树的角度来考虑可能性
树形dp套路第二步:
根据第一步的可能性分析,列出所需要的信息
树形dp套路第三步:
合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构
树形dp套路第四步:
设计递归函数,递归函数是处理以x为头结点的情况下的答案
包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把所有可能性做整合,并且要返回第三步的信息结构
二叉树节点间的最大距离问题(二叉树的直径问题)
从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的节点个数叫做a到b的距离,那么二叉树任意两个节点之间都有距离,求整棵树上的最大距离
力扣原题
分析
整棵树的最大距离分为两种情况
- 根节点x不参与
此时求出左子树和右子树的最大距离 - 根节点x参与
此时最大距离为左树高度+右树高度+1 - 三者中取最大值即为整棵树的最大距离
代码实现
public static int maxDistance(Node head){
return process(head).maxDistance;
}
public static class Info {
int maxDistance; //表示最大距离
int height; //表示树的高度
public Info(int maxDistance, int height) {
this.maxDistance = maxDistance;
this.height = height;
}
}
public static Info treeMaxDistance(Node root) {
if (root == null) {
return new Info(0, 0);
}
Info leftInfo = treeMaxDistance(root.left);
Info rightInfo = treeMaxDistance(root.right);
//info
int p1 = leftInfo.maxDistance;
int p2 = rightInfo.maxDistance;
int p3 = leftInfo.height + rightInfo.height + 1;
int maxDistance = Math.max(Math.max(p1, p2), p3);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
return new Info(maxDistance, height);
}
派对的最大快乐值
分析
代码实现
package Test;
import java.util.List;
/**
* @Author laimouren
* @Date 2021/12/12 10:24
*/
public class MaxHappy {
public static class Employee {
public int happy;// 这名员工可以带来的欢乐值
public List<Employee> nexts;// 这名员工有哪些直接下级
public Employee(int happy, List<Employee> employeeList) {
this.happy = happy;
this.nexts = employeeList;
}
}
public static class Info {
public int laiMaxHappy; // 头结点来的时候最大快乐值
public int buMaxHappy; // 头结点不来时最大快乐值
public Info(int lai, int bu) {
laiMaxHappy = lai;
buMaxHappy = bu;
}
}
public static int maxHappy(Employee boss) {
if (boss == null) {
return 0;
}
Info all = process(boss);
return Math.max(all.laiMaxHappy, all.buMaxHappy);
}
public static Info process(Employee x) {
// 以基层员工作为终止条件
if (x.nexts.isEmpty()) {
return new Info(x.happy, 0);
}
//x来的情况下,整棵树的最大快乐值
int lai = x.happy;
//x不来的情况下,整棵树的最大快乐值
int bu = 0;
for (Employee next : x.nexts) {
Info nextInfo = process(next);
//当x来的时候,下级员工只能不来
lai += nextInfo.buMaxHappy;
//当x不来的时候,下级员工可以来也可以不来,最大快乐值取两者中最大的
bu += Math.max(nextInfo.laiMaxHappy, nextInfo.buMaxHappy);
}
return new Info(lai, bu);
}
}
Morris遍历
一种遍历二叉树的方式,并且时间复杂度为O(N),额外空间复杂度为O(1)
通过利用原树中大量空闲指针的方式,达到节省空间的目的
分析
代码实现
public static void morris(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
//mostRight是cur左孩子
mostRight = cur.left;
//有左树的情况,即情况2
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
//此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
//不为空说明不是第一次来cur
} else {
mostRight.right = null;
}
}
//情况1
cur = cur.right;
}
}
改先序遍历
public static void preMorris(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;
}
//此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
if (mostRight.right == null) {
System.out.println(cur.value);
mostRight.right = cur;
cur = cur.left;
continue;
//不为空说明不是第一次来cur
} else {
mostRight.right = null;
}
}
//没有左子树的情况
else{
System.out.println(cur.value);
}
cur = cur.right;
}
}
改中序遍历
public static void inMorris(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
//mostRight是cur左孩子
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
//此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
//不为空说明不是第一次来cur
} else {
mostRight.right = null;
}
}
System.out.println(cur.value);
cur = cur.right;
}
}
改后序遍历
public static void morrisPos(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){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
printEdge(cur.left);
}
}
cur = cur.right;
}
printEdge(head);
System.out.println();
}
//打印逆序完的二叉树,最后再将其还原
public static void printEdge(Node node){
Node tail =reverseEdge(node);
Node cur = tail;
while (cur != null ){
System.out.print(cur.value+" ");
cur =cur.right;
}
reverseEdge(tail);
}
//逆序一棵树
public static Node reverseEdge(Node node){
Node pre = null;
Node next = null;
while (node != null){
next = node.right;
node.right = pre;
pre = node;
node = next;
}
return pre;
}
判断一棵树是否是搜索二叉树
public static boolean isBST(Node head) {
if (head == null) {
return true;
}
Node cur = head;
Node mostRight = null;
//之前的值
int preValue = Integer.MIN_VALUE;
while (cur != null) {
//mostRight是cur左孩子
mostRight = cur.left;
//有左树的情况,即情况2
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
//此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
//不为空说明不是第一次来cur
} else {
mostRight.right = null;
}
}
//中序遍历时每个元素都是严格递增的,所以如果有<=的情况出现,
//说明不是搜索二叉树
if(cur.value <= preValue){
return false;
}
preValue = cur.value;
cur = cur.right;
}
return true;
}
当二叉树问题需要整合当前节点和左子树和右子树的值才能解决问题时,用二叉树的递归套路
当二叉树问题只需遍历即可找出答案时,用Morris是最优解