代码随想录算法训练营day16

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

参考链接:代码随想录

104.二叉树的最大深度

思路:上次是用层序遍历的思路做过。这次想一点不一样的思路,对于一个二叉树的最大深度其实即为其两个子树的最大深度+1,所以可以采用递归法解决,结束条件即当root都为空的时候,最大深度即为1。时间复杂度O(n)。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root){
            return 0;
        }
        return max(maxDepth(root->left),maxDepth(root->right))+1;
    }
};

可以看到递归法的代码非常简单,但是运行时间比层序遍历长一些。看完解析后认识到这种方法实际上采用的是后续遍历。
真正求深度的逻辑应该采用前序遍历,即从上往下求,按照中->左->右的顺序。
代码如下:

class Solution {
public:
    int result;
    void getdepth(TreeNode* root,int depth){
        result=max(result,depth);//更新最大深度,每次遍历都和当前深度比较
        if(root->left){
            getdepth(root->left,depth+1);
        }
        if(root->right){
            getdepth(root->right,depth+1);
        }
        return;
    }
    int maxDepth(TreeNode* root) {
        if(!root){
            return 0;
        }
        result=0;
        getdepth(root,1);
        return result;
    }
};

111.二叉树的最小深度

思路:之前也用过层序遍历的方法。想想递归法,首先最小深度和最大深度不一样,主要是当左右节点只有一个节点为空的时候,需要直接不考虑这个节点,考虑另一半仅有的节点,而当左右孩子都空,则直接返回1,左右孩子都满,需要取左右最小值后加一。这里需要分三类讨论:左右为空,左右一个空,左右都不空。故一开始就要把根节点是否为空单独考虑。

class Solution {
public:
    int mD(TreeNode* root,int depth){//此时root不为空,depth为此时最小深度
        int result=depth;
        if(!root->left&& !root->right){//两边空,最小深度为1
            result=1;
        }
        else if(!root->left){//左边为空
            result=mD(root->right,result)+1;//只算右边
        }
        else if(!root->right){//右边为空
            result=mD(root->left,result)+1;//只算左边
        }
        else{//两边都有
            result=min(mD(root->left,result),mD(root->right,result))+1;//算两边后去最小值
        }
        return result;
    }
    int minDepth(TreeNode* root) {
        if(!root){
            return 0;
        }
        return mD(root,1);
    }
};

这种写法实际上是前序遍历,采用的回溯法。看了标答后,发现可以把子函数getdepth写成void返回值,将计算的结果单独写成一个全局变量 result ,这样就不需要递归中每次调用函数都返回一个result,在getdepth逐渐调用的过程中全局变量也就完成了计算,节约了空间复杂度。
前序遍历标答:(自己写的)

class Solution {
public:
    int result;
    void getdepth(TreeNode* root,int depth){//此时root不为空,depth为此时深度
        if(!root->left&& !root->right){//两边空,叶子节点返回
            result=result<depth ? result : depth;
        }
        if(root->left){//左边
            getdepth(root->left,depth+1);
        }
        if(root->right){//右边
            getdepth(root->right,depth+1);
        }
        return;
    }
    int minDepth(TreeNode* root) {
        if(!root){
            return 0;
        }
        result=INT_MAX;
        getdepth(root,1);
        return result;
    }
};

标答和自己写的其实有很大区别,没有将两边孩子都不空的时候单独考虑,而是分别考虑左右孩子,只要不为空,则将深度+1后开始计算孩子子树,计算result的时机就是已经到达叶子节点,此时只需要将result和当前遍历的深度比较即可,没有左右子树的比较,result是在前序遍历中逐渐减小的
后序遍历标答:

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(!root){
            return 0;
        }
        int minLeftDepth=minDepth(root->left);//计算左右子树
        int minRightDepth=minDepth(root->right);
        if(root->left&&!root->right){//右空
            return minLeftDepth+1;
        }
        if(!root->left&&root->right){//左空
            return minRightDepth+1;
        }
        return min(minLeftDepth,minRightDepth)+1;
    }
};

后续遍历我一开始没想到,实际上自己写的时候没仔细想是什么遍历。这个答案是比较标准的写法,将左右子树的答案都保存下来,如此递归,然后对左右子树分类讨论而最大深度则不需要分类讨论,这是两题的最大区别。

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

思路:本题一开始最简单的思路就是直接遍历,使用什么方法都可以,然后计数,这对所有普通二叉树都适用。普通方法,一是层次遍历计数,二是类似求深度的写法进行递归,实际上都是遍历方法。所有遍历时间复杂度都是O(n)
层次遍历:

class Solution {
public:
    int countNodes(TreeNode* root) {
        queue<TreeNode*> q;
        int count=0;
        if(root){
            q.push(root);
        }
        while(!q.empty()){
            int size=q.size();
            for(int i=0;i<size;i++){
                TreeNode* node=q.front();
                q.pop();
                if(node->left){
                    q.push(node->left);
                }
                if(node->right){
                    q.push(node->right);
                }
                count++;
            }
        }
        return count;
    }
};

递归:

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(!root){
            return 0;
        }
        return 1+countNodes(root->left)+countNodes(root->right);
    }
};

上面两种方法是通用方法。我们需要利用完全二叉树的性质,看能不能降低一点时间复杂度。这里想了很久没想出来,直接看了解析。首先,要利用完全二叉树的特性,分为两种,一种是满二叉树,对于完全二叉树的满二叉树可以根据一直往左遍历和一直往右遍历深度是否相等来判断;对于非满二叉树,其左右孩子一直遍历,一定会遇到左右子树都是满二叉树的情况。如图所示:可以将一个非满二叉树分解为多个二叉树,使用递归求解。满二叉树的节点个数为2^depth-1。
代码随想录算法训练营day16_第1张图片

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(!root) return 0;
        int leftDepth=0;
        int rightDepth=0;
        TreeNode* tmp=root;
        while(tmp){//计算左深度
            tmp=tmp->left;
            leftDepth++;
        }
        tmp=root;
        while(tmp){//右深度
            tmp=tmp->right;
            rightDepth++;
        }
        if(leftDepth==rightDepth){//是满二叉树
            return pow(2,leftDepth)-1;
        }
        else{
            return 1+countNodes(root->left)+countNodes(root->right);
        }
    }
};

时间复杂度O(logn*logn),前一个logn是计算左右深度,后一个为递归深度。

你可能感兴趣的:(算法,数据结构)