104 二叉树的最大深度
递归法
-
思考:前序与后序在二叉树的遍历
👉 由于前序遍历顺序为中左右,因此在二叉树遍历顺序是从上往下遍历。
👉 而后序遍历顺序为左右中,遍历时候深入到树的底层开始,因此是从下往上的遍历
✅ 因此使用前序求的就是深度,使用后序求的是高度 本题可以使用前序(中左右),也可以使用后序遍历(左右中)。原因是本题求的最大深度 == 根节点的高度
定义补充:
- 二叉树节点的深度:指从根节点到该节点(自上往下)的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点(自下而上)的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
先用后序遍历(左右中)来计算树的高度。 依旧是递归三部曲:
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
代码如下:
public int maxDepth(TreeNode root) {}
- 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
代码如下:
if (root == null) return 0;
- 确定单层递归的逻辑:先求它的左子树的深度(左),再求右子树的深度在(右),最后取左右深度最大的数值(中) 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
代码如下:
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
整体代码如下:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
// 从底部开始遍历 - 后序遍历
int leftHeight = maxDepth(root.left); // 左
int rightHeight = maxDepth(root.right); // 右
int height = 1 + Math.max(leftHeight,rightHeight); // 中
return height;
}
代码精简(使用链式编程)之后代码如下:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
🔴注意:精简之后的代码难以看出是哪种遍历方式以及递归三部曲的步骤,所以如果对二叉树的操作还不熟练,先把思路理顺一步一步写代码,尽量不要直接照着精简代码来学。
迭代法详见:迭代法,运用队列,在层序遍历的基础上加上每次向下一层deep++
拓展:559 n叉树的最大深度
思路与104 类似, 依然可以提供递归法和迭代法,来解决这个问题,这里使用了迭代法。用队列模拟层序遍历。 更详细的迭代思路见上一篇我的文章👈的二叉树的层序遍历
public int maxDepth(Node root) {
if (root == null) return 0;
int depth = 0;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int size = queue.size();
depth++;
for (int i = 0; i < size; i++) {
Node node = queue.poll();
// 取出父节点后遍历子节点集合
for (int i1 = 0; i1 < node.children.size(); i1++) {
if (node.children.get(i1) != null){
queue.offer(node.children.get(i1));
}
}
}
}
return depth;
}
111 二叉树的最小深度
直觉上好像和求最大深度差不多,其实还是差不少的。 本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。具体深度与高度详见104中的分析 那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。 以下讲解中遍历顺序上依然采用后序遍历
❗注意:本题还有一个误区,在处理节点的过程中,最小深度很容易误解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近_叶子节点_的最短路径上的节点数量。注意是叶子节点。
递归法
递归三部曲:
- 确定递归函数的参数和返回值
参数为要传入的二叉树根节点,返回的是int类型的深度。 代码如下:
public int minDepth(TreeNode root) {}
- 确定终止条件
终止条件也是遇到空节点返回0,表示当前节点的高度为0。 代码如下:
if (root == null) return 0;
- 确定单层递归的逻辑
这块和求最大深度可就不一样了,一些同学可能会写如下代码:
int leftDepth = minDepth(node.left);
int rightDepth = minDepth(node.right);
int result = 1 + min(leftDepth, rightDepth); // 错误
return result;
这个代码就犯了此图中的误区:
如果这么求的话,没有左孩子的分支会算为最短深度。
因此题目的意思为
- 如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
- 反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
所以单层逻辑应该如下:
int leftHeight = minDepth(root.left);
int rightHeight = minDepth(root.right);
// 排除情况 - (空 x) (x 空) (空 空)
if (root.left == null && root.right != null) return 1 + rightHeight;
if (root.left != null && root.right == null) return 1 + leftHeight;
int height = Math.min(leftHeight,rightHeight) + 1;
// 对比104 除了取min还需要排除左或右节点有一节点为空情况👆到这里为(x x) 和 (空 空)
return height;
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
整体代码如下:
public int minDepth(TreeNode root) {
if (root == null) return 0;
int leftHeight = minDepth(root.left);
int rightHeight = minDepth(root.right);
// 排除情况 - (空 x) (x 空) (空 空)
if (root.left == null && root.right != null) return 1 + rightHeight;
if (root.left != null && root.right == null) return 1 + leftHeight;
int height = Math.min(leftHeight,rightHeight) + 1;
// 对比104 除了取min还需要排除左或右节点有一节点为空情况👆到这里为(x x) 和 (空 空)
return height;
}
迭代法详见迭代法详解,思路是在层序遍历基础上加上如果左右孩子都为空才到了子节点,返回depth
222 完全二叉树的节点个数
普通二叉树
本题可以用深度优先,广度优先方法遍历所有节点解题,但没利用好**“完全二叉树”**定义接替 首先按照普通二叉树的逻辑来求。 这道题目的递归法和求二叉树的深度写法类似, 而迭代法,记录遍历的节点数量就可以了。 按照这种方法,复杂度如下:
- 时间复杂度:O(n)
- 空间复杂度:O(log n),算上了递归系统栈占用的空间
完全二叉树
思路为:由于完全二叉树上一层是满二叉树
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
代码如下:
public int countNodes(TreeNode root) {}
- 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。如果该树为满二叉树则通过2n - 1公式计算,如果不是则继续递归, 。
代码如下:
if (root == null) return 0;
TreeNode left = root.left;
TreeNode right = root.right;
int leftDepth = 0, rightDepth = 0;
// 遍历左子树深度
while (left != null){
left = left.left;
leftDepth++;
}
// 遍历右子树深度
while (right != null){
right = right.right;
rightDepth++;
}
// 为满二叉树
if (leftDepth == rightDepth) return (2 << leftDepth) - 1;
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
代码如下:
int leftNumber = countNodes(root.left); // 左
int rightNumber = countNodes(root.right); // 右
int result = leftNumber + rightNumber + 1; // 中
return result;
整体代码如下:
public int countNodes(TreeNode root) {
// 利用完全二叉树的定义 优化算法
// 递归结束条件:根节点为null以及遍历完完全二叉树中的满二叉树(不及最后一层)
if (root == null) return 0;
TreeNode left = root.left;
TreeNode right = root.right;
int leftDepth = 0, rightDepth = 0;
// 遍历左子树深度
while (left != null){
left = left.left;
leftDepth++;
}
// 遍历右子树深度
while (right != null){
right = right.right;
rightDepth++;
}
// 为满二叉树
if (leftDepth == rightDepth) return (2 << leftDepth) - 1;
// 单次递归逻辑:
int leftNumber = countNodes(root.left); // 左
int rightNumber = countNodes(root.right); // 右
int result = leftNumber + rightNumber + 1; // 中
return result;
}
(代码可通过链式编程精简)
- 时间复杂度:O(log n × log n)
- 空间复杂度:O(log n
还是那句话,不建议一开始就使用链式编程,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来。
🔴补充:对完全二叉树的定义:
举一个典型的例子如图:
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点_没有满_。
- 对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
- 对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树(一)如图:
完全二叉树(二)如图:
在222题中,可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
这里关键在于如何去判断一个左子树或者右子树是不是满二叉树呢?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:
学习资料: