文章目录
前言
本文章将从很多涉及二叉树来解的算法题中,高度抽象出来一套底层思想,掌握了六步法,可以解决更多算法题。
二叉树递归思想
1)假设以X节点为头,假设可以向X左数和右数要任何信息
2)在上一步的假设下,讨论以X为头节点的数,得到答案的可能性(简单情况是两种分类)
- 和X有关的答案
- 和X无关的答案
3)列出所有可能性后,确定到底需要向左树和右树要什么样的信息
4)把左树信息和右数信息求全集,就是任何一颗子树都需要返回的信息S
5)递归函数都返回S,每一棵子树都这么要求
6)写代码,在代码中考虑如何把左树的信息和右数的信息整合出整棵树的信息
下面将以例题进行渗透!
1.判断平衡二叉树为例。
需要子树的信息:
1.左子树是否为平衡二叉树
2.右子树是否为平衡二叉树
3.左子树的高度
4.右子树的高度
代码
//结构体
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static class Info{
public boolean isBalanced;
public int height;
public Info(boolean i, int h) {
isBalanced = i;
height = h;
}
}
//判断
public static boolean isBalanced2(Node head) {
return process(head).isBalanced;
}
public static Info process(Node x) {
if(x == null) {
return new Info(true, 0);
}
Info leftInfo = process(x.left); //左树提供信息
Info rightInfo = process(x.right); //右数提供信息
int height = Math.max(leftInfo.height, rightInfo.height) + 1; //加工自己的高度信息
boolean isBalanced = true; //加工自己的平衡信息
if(!leftInfo.isBalanced) {
isBalanced = false; //加工自己的平衡信息
}
if(!rightInfo.isBalanced) {
isBalanced = false; //加工自己的平衡信息
}
if(Math.abs(leftInfo.height - rightInfo.height) > 1) {
isBalanced = false; //加工自己的平衡信息
}
return new Info(isBalanced, height); //提交信息
}
2.寻找最大的搜索子树为例。
需要子树的信息:
1.左子树是否搜索树
2.右子树是否为搜索树
3.左子树最大值不超过父节点(X)值
4.右子树最小值不小于父节点(X)值
5.左子树节点数
6.右孩子节点数
注意:
情况1和2都包含的:最大值,最小值
情况1,与X(父节点)无关的答案,即X不是搜索树。
情况2,与X有关,即满足上面前四个条件,就需要的信息:
- X是搜索树
- 最大搜索子树的大小:X的节点个数(左右子树节点数之和再加上自身)
代码
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static int maxSubBSTSize2(Node head) {
if (head == null) {
return 0;
}
return process(head).maxSubBSTSize;
}
// 任何子树
public static class Info {
public boolean isAllBST;
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;
}
//加工自己的max,min
Info leftInfo = process(X.left);
Info rightInfo = process(X.right);
int min = X.value;
int max = X.value;
if(leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
}
if(rightInfo != null) {
min = Math.min(min, rightInfo.min);
max = Math.max(max, rightInfo.max);
}
//加工自己的节数量maxSubBSTSize和判断是否为搜索树isAllBST
int maxSubBSTSize = 0;
if(leftInfo != null) {
maxSubBSTSize = leftInfo.maxSubBSTSize;
}
if(rightInfo !=null) {
maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
}
//开始分情况
boolean isAllBST = false;
if(
// 左树整体需要是搜索二叉树
( leftInfo == null ? true : leftInfo.isAllBST )
&&
( rightInfo == null ? true : rightInfo.isAllBST )
&&
// 左树最大值<x
(leftInfo == null ? true : leftInfo.max < X.value)
&&
(rightInfo == null ? true : rightInfo.min > X.value)
) {
maxSubBSTSize =
(leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
+
(rightInfo == null ? 0 : rightInfo.maxSubBSTSize)
+
1;
isAllBST = true;
}
return new Info(isAllBST, maxSubBSTSize, min, max);
}
3.派对的最大快乐
员工信息的定义如下:
class Employee {
public int happy;//这名员工可以带来的快乐值
List<Employee> subordinates;//这名员工有哪些直接下级
}
公司的每个员工都有符合Employee 类的描述。整个公司的人员结构可以看作是一颗标准的,没有环的多叉树。树的头节点是公司的唯一老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),出基层员工外,每个员工都有一个或镀铬直接下级。
规则:
1.如果某个员工来了,那么这个员工的所有直接下级都不能来
2.派对的快乐值是所有在场的累加
给定一棵树多叉树的头节点boss,请返回派对的最大快乐值。
例如:
boss:X,员工:a,b,c
情况1:X不来
happy =
0 +
max{(a来)子树a的最大值,(a不来)子树a的最大值} +
max{(b来)子树b的最大值,(b不来)子树b的最大值} +
max{(c来)子树c的最大值,(a不来)子树c的最大值}
情况2:X来
happy =
X +
(a不来)子树a的最大值 +
(b不来)子树b的最大值+
(c不来)子树c的最大值
需要的信息
1.在头节点来的时候,整颗子树的最大快乐。
2.在头节点不来的时候,整颗子树的最大快乐。
代码
public static class Employee {
public int happy;
public List<Employee> nexts;
public Employee(int h) {
happy = h;
nexts = new ArrayList<>();
}
}
public static int maxHappy2(Employee head) {
Info allInfo = process(head);
return Math.max(allInfo.no, allInfo.yes);
}
public static class Info {
public int no;
public int yes;
public Info(int n, int y) {
no = n;
yes = y;
}
}
public static Info process(Employee x) {
if (x == null) {
return new Info(0, 0);
}
int no = 0;
int yes = x.happy;
for (Employee next : x.nexts) {
Info nextInfo = process(next);
no += Math.max(nextInfo.no, nextInfo.yes);
yes += nextInfo.no;
}
return new Info(no, yes);
}
4.判断是否为满二叉树
比较简单比分析了,直接上代码
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isFull2(Node head) {
if (head == null) {
return true;
}
Info all = process(head);
return (1 << all.height) - 1 == all.nodes;
}
public static class Info {
public int height;
public int nodes;
public Info(int h, int n) {
height = h;
nodes = n;
}
}
public static Info process(Node head) {
if (head == null) {
return new Info(0, 0);
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
int nodes = leftInfo.nodes + rightInfo.nodes + 1;
return new Info(height, nodes);
}
思想进阶(多情况)
5.判断是否为完全二叉树
判断标准:
1)任意节点有右孩子无左孩子,false
2)一旦遇到左右孩子不双全的节点,其后面的节点有非叶节点,false
非递归,宽度优先遍历解决
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isCBT1(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
// 是否遇到过左右两个孩子不双全的节点
boolean leaf = false;
Node l = null;
Node r = null;
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
l = head.left;
r = head.right;
if (
// 如果遇到了不双全的节点之后,又发现当前节点不是叶节点
(leaf && (l != null || r != null)) || (l == null && r != null)
) {
return false;
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
}
if (l == null || r == null) {
leaf = true;
}
}
return true;
}
递归思想解决
情况1:无缺口,满二叉树。需要信息:
- 子树是否满的,以及高度
结论:当子树是满的,且高度一样,就可以确定整棵树完全二叉树
情况2:有缺口。
1)缺口在左子树,,需要信息:
- 左子树是否为完全二叉树,右子树是否为满二叉树,和高度
结论:左子树为完全,右子树为满,左比右高1层,就可以确定整棵树完全二叉树。
2)缺口在中间,需要信息:
- 左子树是否为满二叉树,右子树是否为满二叉树,和高度
结论:左子树为满,右子树为满,左比右高1层,就可以确定整棵树完全二叉树
3)缺口在右子树,需要信息:
- 左子树是否为满二叉树,右子树是否为完全二叉树,和高度
结论:左子树为满,右子树为满,左右一样高,就可以确定整棵树完全二叉树
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isCBT2(Node head) {
return process(head).isCBT;
}
//是否满的?是否完全?高度
public static class Info {
public boolean isFull;
public boolean isCBT;
public int height;
public Info(boolean full, boolean cbt, int h) {
isFull = full;
isCBT = cbt;
height = h;
}
}
public static Info process(Node x) {
if (x == null) { //空树
return new Info(true, true, 0);
}
Info leftInfo = process(x.left); //左树提供信息
Info rightInfo = process(x.right);
//加工自己信息
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
//开始分情况
boolean isCBT = false;
if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height) {//情况1
isCBT = true;
} else if (leftInfo.isCBT && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {//情况2的1)
isCBT = true;
} else if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {//情况2的2)
isCBT = true;
} else if (leftInfo.isFull && rightInfo.isCBT && leftInfo.height == rightInfo.height) {//情况2的3)
isCBT = true;
}
return new Info(isFull, isCBT, height);
}
6.两个节点的最低公共祖先
非递归,利用哈希表
思路:
建立map表,存储每个节点与父节点。
在利用set表,这个表来存储节点a的所有父节点(循环遍历map);然后将节点b和节点b的父节点(遍历map可得到)在set表里匹配。
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
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 Node lowestAncestor1(Node head, Node o1, Node o2) {
if (head == null) {
return null;
}
// key的父节点是value
HashMap<Node, Node> parentMap = new HashMap<>();
parentMap.put(head, null);
fillParentMap(head, parentMap);
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);
}
return cur;
}
递归思想解决
思路:
节点a,b与某一以X为头节点的树,三者之间无非三种关系:
情况1:a,b都不在X树上
情况2:a,b一个在X树上
情况3:都在X树上时,又分为:
- 1) 左,右子树各一个
- 2)都在左子树
- 3) 都在右子树
- 4)X就是其中之一,另一节点在左子树或右子树
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static class Info {
public boolean findA;
public boolean findB;
public Node ans;
public Info(boolean fA, boolean fB, Node an) {
findA = fA;
findB = fB;
ans = an;
}
}
public static Info process(Node x, Node a, Node b) {
if (x == null) {
return new Info(false, false, null);
}
Info leftInfo = process(x.left, a, b);
Info rightInfo = process(x.right, a, b);
boolean findA = (x == a) || leftInfo.findA || rightInfo.findA;
boolean findB = (x == b) || leftInfo.findB || rightInfo.findB;
Node ans = null;
//寻找最初交汇点
if (leftInfo.ans != null) {
ans = leftInfo.ans; //情况2)在左树上已经交汇了,都在左树
} else if (rightInfo.ans != null) {
ans = rightInfo.ans; //情况3)在右树上已经交汇了,都在右数
} else {
if (findA && findB) {
ans = x; //情况1)和情况4) }
}
//剩余情况1和情况2
return new Info(findA, findB, ans);
}