Day14 二叉树:104.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

142 阅读9分钟

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.二叉树的最大深度”相比,还是有很多细节是不一样的。

这道题依然可以用后序遍历

求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离也同样是最小深度。

看起来前序遍历更符合正常的思考方式,但是代码稍微麻烦一些,前序遍历就是中左右,求深度就是从上往下

前序,或者迭代法都在网站上。

⚠️注意:

14.01.png

题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 ,注意是叶子节点

后序遍历实现:(递归)

经典的递归三部曲:

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);
     }
 };

迭代法:(非递归)