104.二叉树的最大深度
难度指数:😀🙂
代码很简短,你照着题解稀里糊涂抄一下,题目过了,但你对这道题的很多细节并不理解。
因为很多基本的点并没有想清楚。
区分深度和高度:
深度是二叉树中,任意一个节点到根节点的距离(节点数)。
高度是二叉树中,任意一个节点到叶子节点的距离。
Q:在二叉树的遍历中,如何求高度?如何求深度?
A:求高度用后序遍历,求深度用前序遍历。
(求高度是从下往上计数,叶子节点是1,它的父节点是2,……)
内心OS:懵逼了,二叉树只能从上往下遍历,往下遍历一层+1,往下遍历一层+1,这个我会,
但你要求从下往上遍历,逐个+1,就有点懵逼了。
左右中
可以把中放在最后,把处理逻辑放在最后,可能将叶子节点的高度返回给父节点,父节点知道叶子节点的高度之后,父节点+1;
父节点可以将它结果返回给父父节点,这样父父节点知道了它孩子的高度,父父节点+1。
求深度就用中左右
我们用后序遍历来写,你可能会疑惑:我们求的是深度,为什么用后序遍历来写?
🦄关键:根节点的高度就是这颗二叉树的最大深度。
如果我们真正去求深度,那应该用前序遍历。
我们求的是高度,这道题通过求根节点的高度,得出二叉树的最大深度。
写一个后序遍历的代码,感受这个过程;最后再给出一个精简的版本,和网上很多题解的代码就很类似。
代码思路:
递归三部曲
1️⃣确定递归函数的参数和返回值
//无论是要求高度还是深度,返回值都是int
int getheight(node) { //传入根节点,求的是根节点的高度
2️⃣终止条件
if (node == NULL) {
return 0;
}
3️⃣确定单层递归的逻辑
int leftheight = getheight(node->left); //左
int rightheight = getheight(node->right); //右
int height = 1 + max(leftheight, rightheight); //中
return height;
}
我们求的是根节点的高度,根节点的高度就是这颗二叉树的最大深度。
就是将左子树的高度和右子树的高度,两者之间取一个最大值max,+1,就是当前这个父节点的高度。
中间是处理的过程,才能把底下的孩子的结果返回给父节点,父节点再做+1的操作。O(这样就实现了从下往上计数)
AC代码: (核心代码模式)
class Solution {
public:
int getheight(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftheight = getheight(node->left); //左
int rightheight = getheight(node->right); //右
int height = 1 + max(leftheight, rightheight); //中
return height;
}
int maxDepth(TreeNode* root) {
return getheight(root);
}
};
精简版(不推荐):
class Solution {
public:
int getheight(TreeNode* node) {
if (node == NULL) {
return 0;
}
return 1 + max(getheight(node->left), getheight(node->right));
}
int maxDepth(TreeNode* root) {
return getheight(root);
}
};
使用真正求深度的前序遍历也是可以写这道题的,但是网上这道题前序遍历的版本比较少,因为写起来复杂一些。
网站上有代码,前序遍历还涉及到了回溯的过程。
同时,这道题还可以用迭代法来实现。
559.N叉树的最大深度
难度指数:😀🙂
AC代码: (核心代码模式)
class Solution {
public:
int maxDepth(Node* root) {
if (root == NULL) {
return 0;
}
int depth = 0;
for (int i = 0; i < root->children.size(); i++) {
depth = max(depth, maxDepth(root->children[i]));
}
return depth + 1;
}
};
笑死,没看懂
111.二叉树的最小深度
难度指数:😀🙂
和“104.二叉树的最大深度”相比,还是有很多细节是不一样的。
这道题依然可以用后序遍历,
求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离也同样是最小深度。
看起来前序遍历更符合正常的思考方式,但是代码稍微麻烦一些,前序遍历就是中左右,求深度就是从上往下
前序,或者迭代法都在网站上。
⚠️注意:
题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 ,注意是叶子节点。
后序遍历实现:(递归)
经典的递归三部曲:
1️⃣确定递归函数的参数和返回值
2️⃣确定终止条件
3️⃣确定单层递归的逻辑
AC代码: (核心代码模式)
class Solution {
public:
//1️⃣
int getheight(TreeNode* node) {
//2️⃣
if (node == NULL) { //遇到空节点,空节点高度是0
return 0;
}
//3️⃣
int leftheight = getheight(node->left); //左
int rightheight = getheight(node->right); //右
//中
if (node->left == NULL && node->right != NULL) { //左为空,右不为空
return 1 + rightheight; //1 + 取右子树的最小高度
}
if (node->left != NULL && node->right == NULL) {
return 1 + leftheight;
}
int result = 1 + min(leftheight, rightheight);
return result;
}
int minDepth(TreeNode* root) {
return getheight(root);
}
};
222.完全二叉树的节点个数
给我们一棵完全二叉树,让我们返回完全二叉树节点的数量。
如果把这棵树当成是普通的二叉树,那么在递归中,前中后序遍历都可以求该二叉树节点的数量;迭代法中,层序遍历也可以求该二叉树节点的数量。
既然说了是完全二叉树,就是要我们尽量使用到完全二叉树的特性。
若将这道题当成是普通二叉树,可以先写如何计算节点的数量,就是对树做遍历,用哪种遍历方式都行,这里用后序遍历来实现(简单一些)。
普通二叉树解法:后序(递归)
代码思路:
int getNum(node) {
if (node == NULL) {
return 0;
}
//确定单层递归的逻辑
leftnum = getNum(node->left); //左
rightnum = getNum(node->right); //右
result = leftnum + rightnum + 1; //中 左子树节点的数量 + 右子树节点的数量 + 本身节点的数量
ruturn result;
}
时间复杂度:O(n) 每个节点都遍历了一遍
空间复杂度:O(logn),算上了递归系统栈占用的空间
AC代码: (核心代码模式)
class Solution {
public:
int getNum(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftNum = getNum(node->left); //左
int rightNum = getNum(node->right); //右
int result = leftNum + rightNum + 1; //中
return result;
}
int countNodes(TreeNode* root) {
return getNum(root);
}
};
代码经过精简: (不推荐)
class Solution {
public:
int getNum(TreeNode* node) {
if (node == NULL) {
return 0;
}
return 1 + getNum(node->left) + getNum(node->right); //不容易看出是后序遍历,不推荐
}
int countNodes(TreeNode* root) {
return getNum(root);
}
};
完全二叉树解法:(后序)
利用完全二叉树的特性,来计算这棵二叉树节点的数量
我们知道求满二叉树中节点的数量的计算公式:2^k - 1
我们可以用它来辅助计算完全二叉树里面节点的数量:
在遍历二叉树的时候,如果判断其子树是一棵满二叉树,就可以直接用公式来计算,
即先得到子树的深度,如果子树是满二叉树,就可直接用公式计算出子树的节点个数,返回给上一层,
根节点就可以得到左子树的数量和右子树的数量,最后 + 1 得到整棵完全二叉树的节点数量。
🦄关键:如何判断子树是满二叉树,同时又如何求它的深度?
比如根节点的左子树,当一直向左遍历的深度和一直向右遍历的深度是相等的,那么该左子树就是满二叉树。
⚠️注意:满足满二叉树的条件是建立在完全二叉树上面的。 (如果没懂建议回看视频就能琢磨懂了 08:50)
一直向下递归,一定可以遇到符合满二叉树条件的节点,不会进入死循环。
最终叶子节点是一定至少是满二叉树。
思路很明确:一直往左递归,计算它的左侧深度;然后一直往右递归,计算它的右侧深度。
如果这两侧深度相同,说明该子树是满二叉树,明确了是满二叉树,就可以通过公式计算出节点数量,返回给上一层。
建议会看一遍视频
总结:这种方式利用了完全二叉树的特性,同时,也避免了去遍历没有必要的节点
代码思路:
这是个后序遍历的过程
注意:这种二叉树的解法,终止条件还是比较复杂的。不仅仅是遇到为空的情况,还需要判断遇到子树为满二叉树的情况。
int getNum(node) {
if (node == NULL) {
return 0;
}
left = node->left; //定义一个该节点的左指针,指向左侧 (一直往左侧遍历,计算左侧深度)
right = node->right; //定义一个该节点的右指针,指向右侧 (一直往右侧遍历,计算右侧深度)
leftdepth = 0; //统计左侧深度
rightdepth = 0; //统计右侧深度
while (left) { //左指针一直往左下遍历,直到为空
left = left->left;
leftdepth++;
}
while (right) { //右指针一直往右下遍历,直到为空
right = right->right;
rightdepth++;
}
//得到左侧深度和右侧深度,判断是否相等
if (leftdepth == rightdepth) { //说明是满二叉树
return (2 << leftdepth) - 1;
}
//3️⃣确定单层递归的逻辑
leftNum = getNum(node->left); //左
rightNum = getNum(node->right); //右
result = leftNum + rightNum + 1; //中
return result;
}
这个在二叉树大的情况下更省时间,效率更高。
(因为我们并没有遍历这棵完全二叉树的里面所有的节点,而是通过判断子树中的外侧节点中,左侧深度和右侧深度是否相等,
若相等则子树就是满二叉树,直接用公式计算子树的节点数量,然后返回上一层。)
若这颗树足够大,那些中间节点是没有计算的。
时间复杂度:O(logn * logn)
空间复杂度:O(logn)
AC代码: (核心代码模式)
class Solution {
public:
int getNum(TreeNode* node) {
if(node == NULL) {
return 0;
}
TreeNode* left = node->left;
TreeNode* right = node->right;
int leftdepth = 0; //统计左侧深度
int rightdepth = 0; //统计右侧深度
while (left) { //左指针一直往左下遍历,直到为空
left = left->left;
leftdepth++;
}
while (right) { //右指针一直往右下遍历,直到为空
right = right->right;
rightdepth++;
}
//得到左侧深度和右侧深度,判断是否相等
if (leftdepth == rightdepth) { //说明是满二叉树
return (2 << leftdepth) - 1;
}
int leftNum = getNum(node->left); //左
int rightNum = getNum(node->right); //右
int result = leftNum + rightNum + 1; //中
return result;
}
int countNodes(TreeNode* root) {
return getNum(root);
}
};