一、二叉树的最大深度
题目:
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:3
示例 2:
输入:root = [1,null,2] 输出:2
思路:
采用后序遍历的方法,一层一层的去判断子节点与父节点的距离,可以将一整个二叉树转变为一个个独立开来的二叉树,然后分别判断距离,在逐渐返回上一层,直到返回到根节点为止
代码:
public int maxDepth(TreeNode root) {// 如果根节点为空,即空树,深度为 0if (root == null) return 0;// 递归计算左子树的最大深度int leftDepth = maxDepth(root.left);// 递归计算右子树的最大深度int rightDepth = maxDepth(root.right);// 当前节点的深度为左右子树最大深度的较大值加上当前节点的深度 1int depth = Math.max(leftDepth, rightDepth) + 1;// 返回当前节点的深度return depth;
}
- 首先进行判断,如果
root
为null
,即空树,则深度为 0。 - 否则,分别递归计算左子树
root.left
和右子树root.right
的最大深度。 - 使用
Math.max
方法比较左右子树的最大深度,然后加上当前节点的深度 1,得到当前节点为根的子树的最大深度depth
。 - 最后返回计算得到的
depth
值作为结果。 maxDepth
方法通过递归调用自身,深度优先地计算每个节点的深度。- 如果树为空,则最大深度为 0。
- 否则,通过递归分别计算左右子树的深度,并根据子树深度的比较确定当前子树的最大深度。
- 返回的最终深度值代表整棵树的最大深度。
此外代码还可以进行简化
public int maxDepth(TreeNode root) {if (root == null) {return 0;}return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
二、二叉树的最小深度
题目:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6] 输出:5
思路:
代码与最大深度类似,但是有个误区,如果出现了左子树或者右子树一边为null的情况,最终结果就为1了,但是实际上这并不是从根节点到最近叶子节点的最短路径上的节点数量,因此需要额外的做出判断
代码:
public int minDepth(TreeNode root) {if (root == null) {return 0;}// 递归计算左子树的最小深度int leftDepth = minDepth(root.left);// 递归计算右子树的最小深度int rightDepth = minDepth(root.right);// 如果当前节点的左子树为空,右子树不为空,则最小深度为右子树的深度 + 1if (root.left == null && root.right != null) {return 1 + rightDepth;}// 如果当前节点的右子树为空,左子树不为空,则最小深度为左子树的深度 + 1if (root.left != null && root.right == null) {return 1 + leftDepth;}// 否则,当前节点的最小深度为左右子树最小深度的较小值 + 1return 1 + Math.min(leftDepth, rightDepth);
}
-
空树情况:
- 如果
root
为null
,即空树,则最小深度为 0,因此直接返回 0。
- 如果
-
非空树情况:
- 首先递归计算左子树的最小深度
leftDepth
。 - 然后递归计算右子树的最小深度
rightDepth
。
- 首先递归计算左子树的最小深度
-
叶子节点情况:
- 如果当前节点
root
的左子树为空但右子树不为空,则最小深度为右子树的深度 + 1。这是因为要考虑到最小深度是从根节点到叶子节点的路径,所以只有右子树的情况需要特别处理。 - 同理,如果当前节点的右子树为空但左子树不为空,则最小深度为左子树的深度 + 1。
- 如果当前节点
-
一般情况:
- 如果当前节点既有左子树又有右子树,则最小深度为左右子树最小深度的较小值 + 1
三、完全二叉树节点的数量
题目:
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h
层,则该层包含 1~ 2h
个节点。
示例 1:
输入:root = [1,2,3,4,5,6] 输出:6
示例 2:
输入:root = [] 输出:0
示例 3:
输入:root = [1] 输出:1
方法有很多
1、层序遍历
public int countNodes(TreeNode root) {if (root == null) {return 0;}// 递归计算左子树中节点的总数int leftNum = countNodes(root.left);// 递归计算右子树中节点的总数int rightNum = countNodes(root.right);// 当前节点(root)本身加上左右子树的节点数之和return 1 + leftNum + rightNum;
}
时间复杂度为O(n)
2、利用完全二叉树的性质
思路:
由于完全二叉树不存在只有右子树而没有左子树的情况,因此我们可以通过判断左右子树最左边的节点和最右边节点的深度差来判断该二叉树中节点的数量,这样很大一部分中间的节点就没必要再次遍历,时间复杂度自然就小于O(n)了
代码:
public int countNodes(TreeNode root) {if (root == null) {return 0;}// 定义变量保存左子树和右子树TreeNode left = root.left;TreeNode right = root.right;int leftCount = 0, rightCount = 0;// 计算左子树的高度(即左子树的叶子节点数)while (left != null) {left = left.left;leftCount++;}// 计算右子树的高度(即右子树的叶子节点数)while (right != null) {right = right.right;rightCount++;}// 如果左子树的叶子节点数等于右子树的叶子节点数,则说明是满二叉树if (leftCount == rightCount) {// 计算满二叉树的节点总数并返回return (2 << leftCount) - 1;}// 如果不是满二叉树,则递归计算左右子树的节点总数,并加上当前节点int leftNum = countNodes(root.left);int rightNum = countNodes(root.right);return 1 + leftNum + rightNum;
}
-
空树情况:
- 如果
root
为null
,即空树,则节点总数为 0,直接返回 0。
- 如果
-
非空树情况:
- 首先定义
left
和right
变量分别指向根节点的左子树和右子树。 - 使用
leftCount
和rightCount
分别记录左子树和右子树的叶子节点数(即高度)。 - 这里通过
while
循环找到左子树和右子树的最深的叶子节点,从而得到左右子树的高度。
- 首先定义
-
判断满二叉树:
- 如果左子树的叶子节点数
leftCount
等于右子树的叶子节点数rightCount
,则说明当前子树是满二叉树。 - 满二叉树的节点总数可以通过公式
(2 << leftCount) - 1
计算得到,即2^leftCount - 1
。
- 如果左子树的叶子节点数
-
非满二叉树情况:
- 如果左右子树的叶子节点数不相等,则该二叉树不是满二叉树。
- 需要递归计算左右子树的节点总数,并将当前节点加上去(即加 1)。
-
递归调用:
- 如果不是满二叉树,则递归计算左子树和右子树的节点总数,分别存储在
leftNum
和rightNum
中。 - 最终返回的节点总数是当前节点
root
本身加上左右子树的节点总数。
- 如果不是满二叉树,则递归计算左子树和右子树的节点总数,分别存储在
3、迭代法 (BFS)
思路:
利用队列存储完全二叉树的所有节点,最后节点总数
代码:
public int countNodes(TreeNode root) {if (root == null) return 0; // 如果根节点为空,直接返回节点总数为0Queue<TreeNode> queue = new LinkedList<>(); // 创建一个队列用于BFSqueue.offer(root); // 将根节点加入队列int result = 0; // 初始化节点总数为0while (!queue.isEmpty()) { // 当队列不为空时循环int size = queue.size(); // 获取当前队列的大小,即当前层的节点数while (size-- > 0) { // 遍历当前层的所有节点TreeNode cur = queue.poll(); // 出队一个节点result++; // 节点总数加1,表示访问了一个节点// 将当前节点的左右子节点加入队列(如果存在)if (cur.left != null) queue.offer(cur.left);if (cur.right != null) queue.offer(cur.right);}}return result; // 返回节点总数
}
-
初始条件检查:
- 首先检查根节点
root
是否为空。若为空,则整棵树没有节点,直接返回节点总数为0。
- 首先检查根节点
-
队列初始化:
- 创建一个
Queue<TreeNode>
类型的队列queue
,使用LinkedList
实现。
- 创建一个
-
BFS遍历:
- 将根节点
root
加入队列queue
。 - 初始化节点总数
result
为0,用于统计访问过的节点数。
- 将根节点
-
主循环:
- 使用
while (!queue.isEmpty())
进行 BFS 的迭代,直到队列为空。 - 在每次迭代中,获取当前队列的大小
size
,表示当前层的节点数。
- 使用
-
处理当前层节点:
- 通过
size-- > 0
的循环方式遍历当前层的所有节点:- 从队列中取出一个节点
cur
。 - 将
result
增加1,表示访问了一个节点。
- 从队列中取出一个节点
- 通过
-
扩展下一层:
- 检查当前节点
cur
的左右子节点是否存在,如果存在则加入队列queue
中,以便在下一次迭代时处理。
- 检查当前节点
四、平衡二叉树
题目:
给定一个二叉树,判断它是否是
平衡二叉树
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:true
示例 2:
输入:root = [1,2,2,3,3,null,null,4,4] 输出:false
示例 3:
输入:root = [] 输出:true
思路:
是否平衡的前提是要判断左右子树的高度差是否平衡,采用后序遍历的方法,从叶子节点逐层向上排查左右子树高度差,若高度差大于1则不是平衡二叉树
代码:
递归计算每个节点的高度,并判断是否平衡
public int getHeight(TreeNode root) {if (root == null) {return 0; // 空节点的高度为0}int leftHeight = getHeight(root.left); // 获取左子树的高度int rightHeight = getHeight(root.right); // 获取右子树的高度// 如果左子树或右子树不是平衡的,则整棵树也不平衡,返回-1if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {return -1;}// 返回当前节点为根的子树的高度return Math.max(leftHeight, rightHeight) + 1;
}
- 如果
root
为null
,则返回高度0
。 - 分别计算左子树和右子树的高度
leftHeight
和rightHeight
。 - 如果左子树或右子树的高度为
-1
,或者左右子树的高度差大于1
,则返回-1
,表示当前子树不是平衡的。 - 否则,返回当前节点为根的子树的高度,即
Math.max(leftHeight, rightHeight) + 1
。
//调用 getHeight(root) 方法,如果返回的高度不等于 -1,则说明是平衡二叉树;否则,不是平衡二叉树。
public boolean isBalanced(TreeNode root) {return getHeight(root) != -1;
}
这种方法的时间复杂度为 O(n)
,其中 n
是二叉树的节点数,因为每个节点都需要计算其高度一次。
今天的学习就到这里