目录
12. 二叉树类型判断与树DP思路总结 总览
笔记思维导图链接
参考左程云体系算法课程笔记
参考慕课网算法体系课程笔记
常见题目汇总:
1. 完全二叉树的判断
1)完全二叉树定义
- 要么所有子树都是满的,即满二叉树
- 要么最底层从左往右都是满的
- 即,某个节点没有左子节点,那么肯定也不能有右子节点
- 除了最底层可能没有排满的外,其余每层节点都满了,且最后一层的特点是从左往右排
2)完全二叉树的判断题
题意
- 给定一棵二叉树的头节点head,返回这颗二叉树中是不是完全二叉树
题解
- 1.对完全二叉树的判断关键是倒数第二层,找到左右孩子节点不满的位置开始判断
- 因此,层序遍历更方便观察是否为倒数第二层和最低层的情况
- 因此,需要设置变量标志这个父亲节点
- 2.当找到这个不满的父节点后,分两种情况判断
- 第一种是,要判断这个父亲节点是否在倒数第二层,即同一层和下一层所有节点必须是叶子节点,否则一票否决
- 第二种是,要判断是否出现左为null,右不为null的情况,也一票否决
- 3.每层遍历判断完后,要将子节点放入队列,继续下一层的遍历判断
public static boolean isCBT1(Node root) {
if(root == null) return true;
Queue<Node> queue = new LinkedList<>();
boolean leaf = false; // 第一个不满节点标志,后面都为叶子节点
Node left = null;
Node right = null;
queue.add(root); // 层序遍历前提,先有根节点
while(!queue.isEmpty()) {
Node cur = queue.poll();
left = cur.left;
right = cur.right;
if((leaf && (left != null || right != null)) // 遇到不满节点,后面节点不是叶子节点
|| (left == null && right != null)){ // 左子树不满,右子树满
return false;
}
if(left != null) queue.add(left);
if(right != null) queue.add(right);
if(left == null || right == null) leaf = true;
}
return true;
}
复杂度分析
- 与层序遍历的复杂度相同,均为O(n)
2. 平衡二叉树的判断
1)平衡二叉树定义
![img](https://i-blog.csdnimg.cn/blog_migrate/572ef7dcd25d83a6968dc179d7761c61.png)
平衡二叉树,又称AVL树,具有以下性质:
- 可以是一颗空树
- 左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树
2)使用树的动态规划去判断
题意
- 给定一棵二叉树的头节点head,返回这颗二叉树中是不是平衡二叉树
题解
树的动态规划思想:
-
先将左右子问题的解拿到,然后用左右子问题的解来处理当前节点的问题
-
当前问题的解继续向上提供,直到汇总到根节点,判断所有信息
-
实质上是一个后续遍历过程,只是在遍历过程中,将需要的信息进行封装传递
解题步骤:
-
1.先将需要的信息进行封装
- 判断是否为平衡二叉树,需要节点的高度,节点本身是否是平衡二叉树两个信息
-
2.处理用子问题提供的信息解决父问题的逻辑,即f(n)=f(n + 1)
- 递归终止条件,节点为null,null的高度和本身信息可以确定,可以直接返回信息结果
- 拿到左右子节点提供的信息
- 处理当前节点的两个信息
- 节点高度,是左右节点最大高度+1
- 是否为平衡二叉树,排除法,三种情况不是平衡二叉树,排除后,就是平衡二叉树
- 左不是平衡二叉树
- 右不是平衡二叉树
- 左子树与右子树的高度差绝对值超过1
-
3.最后将当前节点处理的信息返回给上级,进一步作为子信息处理上级的问题
public static boolean isBalanced2(Node root) { if(root == null) return true; return dfs(root).isBalanced; } public static class Info{ boolean isBalanced; int height; public Info(boolean isBalanced, int height) { super(); this.isBalanced = isBalanced; this.height = height; } } public static Info dfs(Node node) { if(node == null) return new Info(true, 0); Info leftInfo = dfs(node.left); Info rightInfo = dfs(node.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); }
3. 搜索二叉树的判断
1)搜索二叉树定义
![img](https://i-blog.csdnimg.cn/blog_migrate/d3c30026f4aae0fee4a8dc948ab80973.png)
-
二叉树是一个有序树,中序遍历结果是从小到大的排序结果
- 左子树上所有节点的值均小于根节点的值
- 右子树上所有节点的值均大于根节点的值
- 左右子树也均是二叉搜索树
-
二叉搜索树默认是不包含相同节点,如果一定有,一般处理是将相同节点压缩在一个节点挂在树上
2)使用树的动态规划去判断
题意
- 给定一棵二叉树的头节点head,返回这颗二叉树中是不是搜索二叉树
题解
- 1.先将需要的信息进行封装
- 判断是否为平衡二叉树,需要考虑的情况:
- 左右两颗子树均是搜索树,
- 且左子树的最大值max小于根节点,右子树最小值min大于根节点的值
- 因此,需要子节点(每个节点)提供的信息有:
- 以该节点为根节点的分支树是否是二叉搜索树 isBST
- 该子树中的最小值 min
- 该子树中的最大值 max
- 判断是否为平衡二叉树,需要考虑的情况:
- 2.处理用子问题提供的信息解决父问题的逻辑,即f(n)=f(n + 1)
- 递归终止条件,节点为null,null的信息不好确定,直接返回null,在上级中处理null的情况
- 拿到左右子节点提供的信息
- 处理当前节点的三个信息
- 最小值min,最大值max,根节点分别与左右子树的min,max比较,得出比较值
- 是否为二叉搜索树,排除法,三种情况不是平衡二叉树,排除后,就是搜索二叉树
- 左不是搜索二叉树
- 右不是搜索二叉树
- 左子树的最大值大于根,右子树的最小值小于根
- 3.最后将当前节点处理的信息返回给上级,进一步作为子信息处理上级的问题
public static boolean isBST2(Node root) {
if (root == null)
return true;
return dfs(root).isBST;
}
public static class Info {
boolean isBST;
int min;
int max;
public Info(boolean isBST, int min, int max) {
super();
this.isBST = isBST;
this.min = min;
this.max = max;
}
}
public static Info dfs(Node node) {
if (node == null)
return null;
Info leftInfo = dfs(node.left);
Info rightInfo = dfs(node.right);
boolean isBST = true;
int min = node.value;
int max = node.value;
if (leftInfo != null) {
min = Math.min(min, leftInfo.min);
if (!leftInfo.isBST || leftInfo.max >= node.value)
isBST = false;
}
if (rightInfo != null) {
max = Math.max(max, rightInfo.max);
if (!rightInfo.isBST || rightInfo.min <= node.value)
isBST = false;
}
return new Info(isBST, min, max);
}
4. 二叉树两个节点最大距离
题意
-
给定一棵二叉树的头节点head,任何两个节点之间都存在距离,
返回整棵二叉树的最大距离
题解
-
1.先将需要的信息进行封装
-
判断是否为平衡二叉树,需要考虑的情况:分为与头节点X有关,与X无关两种
-
1.与X无关,最大距离在左最大距离和右子树最大距离中的一个
-
2.与X有关,最大距离经过X节点,则最终距离为X左树的高度+右树的高度+1
-
-
因此,需要子节点(每个节点)提供的信息有:
-
没颗树的最大距离
-
树的高度
-
-
-
2.处理用子问题提供的信息解决父问题的逻辑,即f(n)=f(n + 1)
- 递归终止条件,节点为null,null的信息好确定,最大距离为0,高度为0
- 拿到左右子节点提供的信息
- 处理当前节点的两个信息
- 树的高度:左树高度与右树高度的最大值加1
- 树的最大距离:与X是否有关分为三种情况,取三种情况的最大值
- 与X无关
- 左树的最大距离
- 右树的最大距离
- 与X有关
- 左树的高度+右树的高度+ 1
- 与X无关
-
3.最后将当前节点处理的信息返回给上级,进一步作为子信息处理上级的问题
public static int maxDistance2(Node root) {
if (root == null)
return 0;
return dfs(root).maxDistance;
}
public static class Info {
int maxDistance;
int height;
public Info(int maxDistance, int height) {
super();
this.maxDistance = maxDistance;
this.height = height;
}
}
public static Info dfs(Node node) {
if (node == null)
return new Info(0, 0);
Info leftInfo = dfs(node.left);
Info rightInfo = dfs(node.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
int p1 = leftInfo.maxDistance;
int p2 = rightInfo.maxDistance;
int p3 = leftInfo.height + rightInfo.height + 1;
int maxDistance = Math.max(p1, Math.max(p2, p3));
return new Info(maxDistance, height);
}
5. 满二叉树的判断
1)满二叉树定义
![img](https://i-blog.csdnimg.cn/blog_migrate/0599dd9cb4a61ed1ed0d672f88fdfce0.png)
-
二叉树每一层节点都是满的
- 即一颗二叉树要么最底层是叶子节点,要么必须有两个子节点
-
高度h和节点个数N满足关系:
- 2 ^ h - 1 = N
2)使用树的动态规划去判断
题意
- 给定一棵二叉树的头节点head,返回这颗二叉树中是不是满二叉树
题解
- 1.先将需要的信息进行封装
- 判断是否为平衡二叉树,需要考虑的情况:
- 节点个数与高度的关系
- 因此,需要子节点(每个节点)提供的信息有:
- 节点个数
- 树的高度
- 判断是否为平衡二叉树,需要考虑的情况:
- 2.处理用子问题提供的信息解决父问题的逻辑,即f(n)=f(n + 1)
- 递归终止条件,节点为null,null信息好确定,节点个数为0, 高度为0
- 拿到左右子节点提供的信息
- 处理当前节点的三个信息
- 节点个数:为左右两个子节点个数之和再加1
- 节点高度:为左右节点高度最大值加1
- 3.最后将当前节点处理的信息返回给上级,进一步作为子信息处理上级的问题
// 第一种方法
// 收集整棵树的高度h,和节点数n
// 只有满二叉树满足 : 2 ^ h - 1 == n
public static boolean isFull1(Node root) {
if (root == null)
return true;
Info info = dfs(root);
return (1 << info.height) - 1 == info.size;
}
public static class Info {
int size;
int height;
public Info(int size, int height) {
super();
this.size = size;
this.height = height;
}
}
public static Info dfs(Node node) {
if (node == null)
return new Info(0, 0);
Info leftInfo = dfs(node.left);
Info rightInfo = dfs(node.right);
int size = leftInfo.size + rightInfo.size + 1;
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
return new Info(size, height);
}
6. 二叉树中最大的二叉搜索子树大小
题意
- 给定一棵二叉树的头节点head,
返回这颗二叉树中最大的二叉搜索子树的头节点
例如:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/86baf2b60dfda673211e6341bcb73446.png)
- 最大的搜索二叉子树的大小是4
题解
- 1.先将需要的信息进行封装
- 判断是否为平衡二叉树,需要考虑的情况:X做头,和X不做头两种情况
- 1.X不做头节点
- 整颗树不是搜索树,左右子树里面有
- 最大搜索树个数是左子树里面的最大搜索树个数
- 最大搜索树个数是右子树里面的最大搜索树个数
- 2.X做头节点
- 表明整颗树都是二叉搜索树,并且要计算出整颗树的节点个数
- 左树是搜索树,且左树的最大值小于X的值
- 右树是搜索树,且右树的最小值大于X的值
- 返回的结果是左子树个数与右子树个数相加再加1
- 1.X不做头节点
- 因此,需要子节点(每个节点)提供的信息有:
- 总的节点个数allSize
- 最大搜索树的个数:maxBSTSubtreeSize,与allSize判断可确定是否整颗树是搜索树
- 最小值min
- 最大值max
- 判断是否为平衡二叉树,需要考虑的情况:X做头,和X不做头两种情况
- 2.处理用子问题提供的信息解决父问题的逻辑,即f(n)=f(n + 1)
- 递归终止条件,节点为null,null信息不好确定,交给上级处理
- 拿到左右子节点提供的信息
- 处理当前节点的四个信息
- 总的节点个数allSize,左右节点总数相加+ 1
- 最大搜索树的个数:
- X不做头节点,就是左右子树最大搜索树个数中的最大一个
- X做头节点,就是左右子树累加再加1
- 前提是先判断X能否做头,即整颗树是否是搜索树
- 最小值min,与左子树比较
- 最大值max,与右子树比较
- 3.最后将当前节点处理的信息返回给上级,进一步作为子信息处理上级的问题
public static int maxSubBSTSize2(Node root) {
if(root == null) return 0;
return dfs(root).maxSubBSTSize;
}
public static class Info {
int maxSubBSTSize;
int allSize;
int min;
int max;
public Info(int maxSubBSTSize, int allSize, int min, int max) {
super();
this.maxSubBSTSize = maxSubBSTSize;
this.allSize = allSize;
this.min = min;
this.max = max;
}
}
private static Info dfs(Node node) {
if(node == null) return null;
Info leftInfo = dfs(node.left);
Info rightInfo = dfs(node.right);
int allSize = 1; // 注意不能直接累加左右子树,要判null
int min = node.value;
int max = node.value;
// 为了求maxSubBSTSize,分为三种情况
// 前两种情况是X不做头节点的情况,分别求左右子树的最大个数
int p1 = -1;
int p2 = -1;
int p3 = -1;
if(leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
allSize += leftInfo.allSize;
p1 = leftInfo.maxSubBSTSize;
}
if(rightInfo != null) {
max = Math.max(max, rightInfo.max);
min = Math.min(min, rightInfo.min);
allSize += rightInfo.allSize;
p2 = rightInfo.maxSubBSTSize;
}
//第三种情况,要判断整颗树是不是二叉搜索树,否则没有第三章情况的值
boolean leftBST = leftInfo == null ? true :
leftInfo.maxSubBSTSize == leftInfo.allSize;
boolean rightBST = rightInfo == null ? true :
rightInfo.maxSubBSTSize == rightInfo.allSize;
if(leftBST && rightBST) {
boolean leftMaxLessX = leftInfo == null ? true :
(leftInfo.max < node.value);
boolean rightMinMoreX = rightInfo == null ? true :
(rightInfo.min > node.value);
if(leftMaxLessX && rightMinMoreX) {
int leftSize = leftInfo == null ? 0 : leftInfo.allSize;
int rightSize = rightInfo == null ? 0 : rightInfo.allSize;
p3 = leftSize + rightSize + 1;
}
}
int maxSubBSTSize = Math.max(p1, Math.max(p2, p3));
return new Info(maxSubBSTSize, allSize, min, max);
}
复杂度分析
- 与二叉树的后续遍历复杂度一致,均是对每个节点访问了三次,O(n)