Day16【二叉树】104.二叉树的最大深度、559.n叉树的最大深度、111.二叉树的最小深度、222.完全二叉树的节点个数

104.二叉树的最大深度

文章讲解 

力扣题目链接 

区别一下深度和高度

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或节点数(取决于深度从0开始还是从1开始)
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或节点数(取决于高度从0开始还是从1开始)

而根节点的高度就是二叉树的最大深度,也称为树的高度

我们通过递归求的根节点高度来求的二叉树的高度

首先思考递归调用能干什么事:这个递归调用能够得到树的高度

然后递归三部曲

确定参数和返回值:这个递归调用能够得到树的高度,总要传进去一个根节点表示这棵树,返回一个高度

int maxDepth(TreeNode* root)

 确定终止条件:这个递归调用能够能够得到树的高度,当传入的节点为空,空树,直接返回高度0

if (root == nullptr) return 0;

确定单层递归的逻辑:这个递归调用能够能够得到树的高度,很容易想到一棵树的高度或为其左子树高度 + 1,或为其右子树高度 + 1,应该取这两个值中最大的那个作为整棵树的高度。而获取左子树高度和右子树高度的工作可以利用本身递归函数,因为这个递归函数就有这样得到树高度的能力

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {    // 虽然命名为maxDepth,但是可以理解为这个函数功能为获取树的高度
        if (root == nullptr) return 0;
        int leftDepth = maxDepth(root -> left);    // 获取左子树高度    
        int rightDepth = maxDepth(root -> right);    // 获取右子树高度
        return max(leftDepth + 1, rightDepth + 1);
    }
};

此外,该函数先获取了左子树高度,再获取了右子树高度,然后返回该函数(处理根节点的函数)比较左子树高度和右子树高度。这种其实就是一种后序遍历的过程(左右根),在处理根节点的时候需要用到遍历左子树和右子树的结果,这是后序遍历的一大优势

559.n叉树的最大深度

力扣题目链接 

同样的思路,求某棵树的高度等于其所有子树中高度的最大值 + 1

求子树高度可以递归调用本身

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    int maxDepth(Node* root) {
       if (root == nullptr) return 0;
       int maxChildrenDepth = 0;
       for (auto & node : root -> children) {
           int childrenDepth = maxDepth(node);
           maxChildrenDepth = max(childrenDepth, maxChildrenDepth);
       } // 获取所有子树高度中的最大值
       return maxChildrenDepth + 1;    // 后序处理
    }
};

111.二叉树的最小深度

力扣题目链接 

文章讲解 

如果是按照上一道题的解法:定义递归函数作用为得到二叉树的最小深度

很容易写出以下代码

class Solution {
public:
    int minDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        int leftDepth = minDepth(root -> left);
        int rightDepth = minDepth(root -> right);
        return min(leftDepth + 1, rightDepth + 1);
    }
};

确定递归函数的作用是获取二叉树最小深度,然后确定终止条件,然后处理逻辑,看似没啥问题

但是注意题目描述:最小深度是从根节点到最近叶子节点的最短路径上的节点数量

 

如图,这棵树的最小深度应该是3.如果用上面的代码,在遍历到根节点的时候,发现根的左子树为空,左子树的最小深度为0,接下来min一定是取遍历左子树得到的结果0 + 1,返回就是1了。不符合题意

根本原因在于当遇到子树为空的时候,min可能会做出错误的返回 

怎么办呢?例如,还是上图,左子树为空,我们希望不要返回左子树最小深度0 + 1,而返回右子树的最小深度 + 1 

我们只需要添加处理这种情况的逻辑就行了

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        int leftDepth = minDepth(root -> left);
        int rightDepth = minDepth(root -> right);
        if (root -> left == nullptr)    // 处理子树为空的情况
            return rightDepth + 1;
        if (root -> right == nullptr)
            return leftDepth + 1;
        return min(leftDepth + 1, rightDepth + 1);
    }
};

222.完全二叉树的节点个数

力扣题目链接 

文章讲解 

一开始想到了层序遍历,直接套用层序遍历的模板(Day15)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int countNodes(TreeNode* root) {
        queue<TreeNode*> que;
        int res = 0;
        if (root != nullptr) que.push(root);
        while (!que.empty()) {    // 准备一层一层遍历
            int size = que.size();    // 一些准备工作
            for (int i = 0; i < size; ++i) {    // 遍历该层各个节点
                TreeNode * node = que.front();
                que.pop();
                if (node -> left) que.push(node -> left);
                if (node -> right) que.push(node -> right);    // 遍历节点:出队列,并入队列其子
            }
            res += size;    // 将该层遍历的节点个数加入res
        }
        return res;
    }
};

又想到了用递归:明确递归函数的作用为返回树的节点个数,然后递归三部曲,后序遍历代码如下

class Solution {
public:
    int countNodes(TreeNode* root) {
        if (root == nullptr) return 0;
        int left = countNodes(root -> left);
        int right = countNodes(root -> right);
        return left + right + 1;    // 树的节点个数为左子树节点数+右子树节点数+1(1代表根节点本身)
    }
};

这两种方式将整棵树每个节点遍历了一遍,时间复杂度为O(n)

但是,上面两种方式, 都没用到完全二叉树这个条件

如何利用?

完全二叉树的两种情况:

  • 满二叉树,可以直接用 2^{depth}-1 来计算
  • 最后一层叶子节点没有满:分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后再套公式

我们的思路其实就是在上面递归的基础上增加终止情况

上面的递归中,终止条件设置为:遇到根为空,直接返回0

我们增加终止情况:如果树为满二叉树,也能直接返回带入公式的结果 

这里就涉及到一个判定满二叉树及获取满二叉树深度的方法

当一个树为满二叉树,当且仅当递归向左遍历的深度等于递归向右遍历的深度(即将上图两根红线的长度应该相等)

因此,我们代码的逻辑如下

  1. 终止条件:root为空直接返回0
  2. 递归向左遍历到底,获得深度
  3. 递归向右遍历到底,获得深度
  4. 如果上面两个深度相等,说明是满二叉树,终止条件:返回带入公式计算出的节点个数
  5. 递归获取左孩子节点个数
  6. 递归获取右孩子节点个数
  7. 返回左孩子节点个数  + 右孩子节点个数 + 1
class Solution {
public:
    int countNodes(TreeNode* root) {
        if (root == nullptr) return 0;
        TreeNode* left = root->left;
        TreeNode* right = root->right;
        int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
        while (left) {  // 求左子树深度
            left = left->left;
            leftDepth++;
        }
        while (right) { // 求右子树深度
            right = right->right;
            rightDepth++;
        }
        if (leftDepth == rightDepth) {
            return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        }    // 上面部分都是在讨论终止条件
        return countNodes(root->left) + countNodes(root->right) + 1;
    }
}

回顾总结  

深度与高度的区别与联系

巩固一下明确递归函数的作用及递归三部曲 

以后做完题目后要回顾一下,本题用的是哪种遍历方式(层序还是深度优先,深度优先的话是哪种顺序)

后序遍历的特点:在遍历根节点的时候已经有了遍历左右子树的返回结果

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林沐华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值