Studying-代码随想录训练营day15| 222.完全二叉树的节点个数、110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

第十五天,二叉树part03💪,编程语言:C++

目录

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

110.平衡二叉树

257.二叉树的所有路径

404.左叶子之和

总结 


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

文档讲解:代码随想录完全二叉树的节点个数

视频讲解:手撕完全二叉树的节点个数

题目:

学习:

1. 根据完全二叉树的定义,我们可以把二叉树分为一个个满二叉树。满二叉树的定义为除叶子节点外,其余节点均含有左右两个孩子,此时节点的个数为2^height - 1;height就是这个满二叉树的高度。

2. 那如何确定是否是满二叉树呢,本题我们可以借助完全二叉树的定义,由于完全二叉树的特点,一个节点一定会先有它的左孩子,再有它的右孩子。因此倘若一棵树的一直往左遍历的深度,与一直往右遍历的深度相同,则说明这是一颗满二叉树,因为中间的节点一定是填满的,否则往右的深度一定时小于往左的深度。

3. 确定以上两点后,如何把一个二叉树分为一个个满二叉树。本题我们可以采用后序遍历的方法,在向下移动的过程中,我们可以每次判断该节点是否为一棵满二叉树的根节点(采用的方式就是2中所说的判断向左和向右的深度是否一样),如果是则可以通过满二叉树的式子直接返回该树的节点数,如果不是则继续往下。注意叶子节点在这也被我们看作成了一颗满二叉树,高度为1,节点个数为1。

代码:

//时间复杂度O(logn*logn)
//空间复杂度O(logn)
class Solution {
public:
    int countNodes(TreeNode* root) {
        //根节点为空,返回0
        //终止条件
        if (root == nullptr) return 0;
        TreeNode* left = root->left;
        TreeNode* right = root->right;
        int leftheight = 0; 
        int rightheight = 0;
        while(left) {
            leftheight++;
            left = left->left;
        }
        while(right) {
            rightheight++;
            right = right->right;
        }
        //如果两边深度一样,则说明是一个完全二叉树,完全二叉树的节点数量为2^(leftheight + 1) - 1 
        if (leftheight == rightheight) return (2 << leftheight) - 1;

        //不满足终止条件的话,进入递归循环
        int leftnum = countNodes(root->left); //遍历左子树
        int rightnum = countNodes(root->right); //遍历右子树
        return 1 + leftnum + rightnum;
    }
};

注意:本题也可以采用普通二叉树求节点数量的方式,采用层次遍历,时间复杂度为O(n)。


110.平衡二叉树

文档讲解:代码随想录平衡二叉树

视频讲解:手撕平衡二叉树

题目:

学习:平衡二叉树指的是,任意一个节点的左右子树的高度差不大于1。依据这个特点,我们可以采取后序遍历的方式,遍历每一个子树的高度,并且当存在一个子树的高度差大于2时,就可以立刻返回-1,不用继续遍历下去。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    //1.确定返回值,参数列表。
    //返回值:由于递归过程中需要比较左右子树的高度,所有返回值应该为int
    //参数:比较根节点左右子树的高度,因此只需要传入根节点即可
    int Height(TreeNode* root) {
        //2.确定终止条件、单层递归逻辑
        //①如果根节点为空,返回长度为0
        if (root == nullptr) return 0;
        //②如果已得知左子树不平衡,返回-1;
        int leftheight = Height(root->left);
        if (leftheight == -1) return -1;
        //③如果已得知右子树不平衡,返回-1;
        int rightheight = Height(root->right);
        if (rightheight == -1) return -1;
        return abs(leftheight - rightheight) > 1 ? -1 : (1 + max(leftheight, rightheight));

    } 
    bool isBalanced(TreeNode* root) {
        if (Height(root) == -1) return false;
        return true;
    }
};

注意:

  1. 此题不适合使用前序遍历,前序遍历一般用于求树的深度,是自顶向下的过程。 因此每次比较一个子树的深度时都需要遍历所有的子节点,时间复杂度会达到O(n^2)。后序遍历则不同,是自底向上的过程,在遍历的过程中,从底部开始比较,并把比较的结果不断往上传递,因此只需要遍历一遍节点即可。
  2. 此题也不适合使用迭代的方式,本题存在回溯比较的过程,使用迭代法会使得代码很复杂,且不利于实现回溯的过程,虽然递归一般来说都能用迭代来实现,但是也需要分析使用的场景。

257.二叉树的所有路径

文档讲解:二叉树的所有路径

视频讲解:手撕二叉树的所有路径

题目:

学习:

1. 本题要找到所有路径,因此必定需要遍历所有的节点,同时每条路径都是从根节点开始的,因此显然本题适合采用前序遍历来进行节点的遍历,遍历到下一个节点的时候,其父节点的信息就已经载入。

2. 同时本题存在大量的回溯过程,即找到一条路径后,要回到一个拐点(中间节点),再去寻找另一条路路径。回溯是下一章节的重点内容,其主要的实现方式就是递归实现,因此本题采用前序遍历的递归形式。

3. 本题在递归上有两个主要注意的点:①本题终止条件不再是找到空节点,而是找到叶子节点这条路径就可以终止了;②本题采用前序遍历,但是对节点的处理要放在终止条件判断前,因为每遍历到一个节点就需要把它加入到字符串当中。

代码:

//时间复杂度O(n^2)
//空间复杂度O(n^2)
class Solution {
public:
    //注意path不能使用引用的方式,这种赋值的方式,不会改变传进来的值,因此会实现自动回溯
    void traversal(TreeNode* cur, string path, vector<string>& result) {
        path += to_string(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
        path += "->";
        if(cur->left == nullptr && cur->right == nullptr) {
            //把最后多余的箭头pop()掉
            path.pop_back();
            path.pop_back();
            result.push_back(path);
        }

        if(cur->left) {  //左
            traversal(cur->left, path, result);
        }
        
        if(cur->right) {  //右
            traversal(cur->right, path, result);
        }
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        traversal(root, "", result);
        return result;
    }
};

注意:上面是使用了拷贝构造string path的方式,每一个递归拷贝了自己的一份path,以此来实现回溯过程。但也能够使用引用的方式,大家公用一份数组,但要注意此时需要自己进行回溯。

class Solution {
private:

    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
        path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中 
        // 这才到了叶子节点
        if (cur->left == NULL && cur->right == NULL) {
            string sPath;
            for (int i = 0; i < path.size() - 1; i++) {
                sPath += to_string(path[i]);
                sPath += "->";
            }
            sPath += to_string(path[path.size() - 1]);
            result.push_back(sPath);
            return;
        }
        if (cur->left) { // 左 
            traversal(cur->left, path, result);
            path.pop_back(); // 回溯
        }
        if (cur->right) { // 右
            traversal(cur->right, path, result);
            path.pop_back(); // 回溯
        }
    }

public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

总结:回溯的过程,实际上就是把上一个循环的所有数据环境保存下来,再回到上一个循环的时候,保证环境不变的过程。可以通过把递归放入栈中,体会回溯。


404.左叶子之和

文档讲解:代码随想录左叶子之和

视频讲解:手撕左叶子之和

题目:

学习:本题需要找到所有的左叶子节点。左叶子节点的特点:1.是叶子节点,2.是父节点的左孩子。根据这两个特点来进行递归构造。

代码:前序遍历(递归)

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    // 1.确定返回值和参数列表,这里我们使用一个sum来计算总和,因此不需要返回值。
    void sumLeft(int& sum, TreeNode* root) {
        //左叶子节点的定义,1.是父节点的左节点,2.是叶子节点
        //2.确定终止条件
        if(root == nullptr) return;
        //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
        if(root->left == nullptr && root->right == nullptr) return;

        //3.确定单层递归逻辑
        //找到左叶子节点的父节点
        if(root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr) {
            sum += root->left->val;
        }
        //如果没找到左叶子节点的父节点,则向下遍历
        sumLeft(sum, root->left);
        sumLeft(sum, root->right);
    }
    int sumOfLeftLeaves(TreeNode* root) {
        int sum = 0;
        sumLeft(sum, root);
        return sum;
    }
};

注意:本题也可以采用后序遍历的方式。采用后序的遍历,返回值设为int型,从底部开始把左叶子节点的值累加并传递给父节点。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right== NULL) return 0;

        int leftValue = sumOfLeftLeaves(root->left);    // 左
        if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
            leftValue = root->left->val;
        }
        int rightValue = sumOfLeftLeaves(root->right);  // 右

        int sum = leftValue + rightValue;               // 中
        return sum;
    }
};

总结 

今天的题重在对递归的使用,和对递归三大条件的设计上。

  1. 完全二叉树的节点个数:通过对递归条件终止条件的特殊处理,讲时间复杂度下降。
  2. 平衡二叉树:重点在于后序遍历求得树的高度,前序遍历求得树的深度,要根据需求进行选择。
  3. 二叉树的所有路径:重点在于对回溯的理解,要找到所有的路径,需要保存父节点的信息。同时由于每个节点遍历的时候就需要立马加入路径队列,因此还需要把对节点的处理放在终止条件的判断前。
  4. 左叶子之和:有两种不同的方法,根据左叶子节点的特点构造递归三部曲。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/727997.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

118 杨辉三角

题目 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 解析 就是模拟法&#xff0c;没有什么特殊的…

从仓位出发 略谈在线伦敦银交易的技巧

现在我们做伦敦银投资&#xff0c;都是通过线上完成的。在线做伦敦金交易的意思&#xff0c;就是投资者通过网络&#xff0c;在PC端或者移动端去利用交易软件完成交易&#xff0c;那么在线伦敦银交易有什么技巧呢&#xff1f;下面我们就从仓位的角度来讨论。 投资者入场后所持有…

Vulhub——Log4j、solr

文章目录 一、Log4j1.1 Apache Log4j2 lookup JNDI 注入漏洞&#xff08;CVE-2021-44228&#xff09;1.2 Apache Log4j Server 反序列化命令执行漏洞&#xff08;CVE-2017-5645&#xff09; 二、Solr2.1 Apache Solr 远程命令执行漏洞&#xff08;CVE-2017-12629&#xff09;2.…

Java 笔记:常见正则使用

文章目录 Java 笔记&#xff1a;常见正则使用正则简介常用匹配年月日的时间匹配手机号码校验 参考文章 Java 笔记&#xff1a;常见正则使用 正则简介 正则表达式定义了字符串的模式。 正则表达式可以用来搜索、编辑或处理文本。 正则表达式并不仅限于某一种语言&#xff0c;但…

Freemaker 模板

背景 发送邮件&#xff0c;正文利用freemaker完成 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId> </dependency>Autowired private Configuration configurer;GetMap…

Set集合系列——Set、HashSet、LinkedHashset、TreeSet

Set系列的公共特点&#xff1a;无重复、无索引&#xff0c;不可用普通for循环&#xff0c;API和Collection重复 HashSet&#xff1a;采取哈希表存取数据 哈希表组成&#xff1f; JDk8之前&#xff1a;数组链表&#xff0c; JDK8以后&#xff1a;数组链表红黑树 哈希值&#…

【Python】使用matplotlib绘制图形(曲线图、条形图、饼图等)

文章目录 一、什么是matplotlib二、matplotlib 支持的图形三、如何使用matplotlib1. 安装matplotlib2. 导入matplotlib.pyplot3. 准备数据4. 绘制图形5. 定制图形6. 显示或保存图形7. &#xff08;可选&#xff09;使用subplots创建多个子图注意事项&#xff1a; 四、常见图形使…

CCF推荐会议必投攻略:这些顶级会议投完直通录取大门

CCF推荐会议必投攻略&#xff1a;这些顶级会议投完直通录取大门&#xff01; 会议之眼 快讯 CCF介绍 CCF&#xff08;China Computer Federation&#xff09;即中国计算机学会&#xff0c;前身是中国电子学会计算机专业委员会&#xff0c;成立于1962年。这是由从事计算机及相…

idea2023开发插件入门

idea2023开发插件入门 创建工程 通过 idea plugin 来创建工程 修改 开发语言 默认创建的工程是用scala开发的&#xff0c;但是我不会&#xff0c;就会java,所以改成java创建 build.gradle.kt 为 build.gradlesettings.gradle.kt 为 settings.gradle build.gradle修改为以…

食品安全无小事:EasyCVR+AI技术助力食品加工厂管理透明化,构建食品安全防线

一、背景需求 近期有新闻记者曝光某省禽类屠宰加工厂脏乱差问题严重&#xff0c;工人脚踩鹅肠鸭肠混杂洗地水、烟头随手扔进鸭肠筐、污水捞出死鸭再上生产线…卫生情况十分堪忧。食品卫生安全频频出现负面新闻&#xff0c;如何实现源头治理&#xff1f;如何将各类食品安全风险隐…

聊聊 oracle varchar2 字段的gbk/utf8编码格式和字段长度问题

聊聊 oracle varchar2 字段的gbk/utf8编码格式和字段长度问题 1 问题现象 最近在排查某客户现场的数据同步作业报错问题时&#xff0c;发现了部分 ORACLE 表的 varchar2 字段&#xff0c;因为上游 ORACLE数据库采用 GBK 编码格式&#xff0c;而下游 ORACLE 数据库采用UTF8 编…

开发一个软件自动运行工具不可缺少的源代码分享!

在软件开发领域&#xff0c;自动运行工具扮演着至关重要的角色&#xff0c;它们能够简化软件部署、提升运行效率&#xff0c;并在很大程度上降低人为操作失误的可能性。 而一个高效的自动运行工具的背后&#xff0c;往往是经过精心设计与实现的源代码在默默支撑&#xff0c;本…

html做一个画柱形图的软件

你可以使用 HTML、CSS 和 JavaScript 创建一个简单的柱形图绘制软件。为了方便起见&#xff0c;我们可以使用一个流行的 JavaScript 图表库&#xff0c;比如 Chart.js&#xff0c;它能够简化创建和操作图表的过程。 以下是一个完整的示例&#xff0c;展示如何使用 HTML 和 Cha…

代码随想录-Day36

452. 用最少数量的箭引爆气球 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂…

byte[]转MultipartFile、byte[]转File一次看个够

目录 需求背景 当你需要将byte[]、MultipartFile、File实现互转时&#xff0c;无外乎以下场景&#xff1a; 保存第三方接口返回二进制流前/后端文件流上传微服务间调用文件格式转换 正如你所需要的&#xff0c;通过搜索引擎筛选到我的本篇文章是因为你在开发中需要将byte[]转…

10万人寻梦玩具好莱坞 ,一人逆袭年销1.8个亿

文丨王一粟 周效敬 奥特曼、四驱赛车、毛绒玩具......如果有一个地方能实现玩具自由&#xff0c;那一定是广东澄海。 澄海是中国乃至世界的玩具之都&#xff0c;给亿万的孩子甚至长大了的孩子带来无数快乐&#xff0c;堪称玩具好莱坞。 无数的年轻人怀揣着实现财富自由的梦想…

【机器学习】机器的登神长阶——AIGC

目录 什么是AIGC 普通用户接触AIGC网站推荐 通义千问 白马 普通用户如何用好AIGC 关键提示词的作用 AIGC的影响 就业市场&#xff1a; 教育领域&#xff1a; 创意产业&#xff1a; 经济活动&#xff1a; 社交媒体与信息传播&#xff1a; AIGC面临的挑战 什么是AIGC…

Linux--视频推流及问题

方案一&#xff1a; mjpg-streamer,它运行在ARM板上 在手机上使用浏览器直接观看视频 方案二&#xff1a; 推流端&#xff08;Fmpeg&#xff09;--rtmp-->Nginx&#xff08;流媒体服务器&#xff09;--rtmp/httpflv/hls-->浏览器、播放器 此篇文章记录方案二的具体细…

Vue中使用ElementUI组件Form组件的校验validate

先准备一些el-form元素 这里面el-form中:model(v-bind:model)是单项绑定的&#xff0c;如果你写成了v-model""可能会出现校验没有效果的情况。 这是校验过后的结果了 现在开始使用下吧&#xff01; 1.在el-form中绑定一个ref&#xff0c;名字自拟,后续触发检验结果…

Java实现俄罗斯方块——文本域组件

技术实现&#xff1a; 1.初始化游戏窗口&#xff1b; 2.初始化游戏界面&#xff1b; 3.初始化游戏的说明面板&#xff1b; 4.随机生成下落方块&#xff1b; 5.绘制方块&#xff1b; 6.清除方块&#xff1b; 7.清楚某一行方块&#xff0c;上方方块掉落&#xff1b; 8.刷新…