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

news2024/12/23 22:09:46

第十五天,二叉树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. 左叶子之和:有两种不同的方法,根据左叶子节点的特点构造递归三部曲。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mistral AI 发布 Codestral-22B,精通 80+ 编程语言,22B 参数超越 70B Code Llama

前言 大型语言模型 (LLM) 在代码生成领域展现出巨大的潜力&#xff0c;但现有的模型在支持的编程语言数量、生成速度和代码质量方面仍存在局限性。法国 AI 独角兽 Mistral AI 近期发布了其首款代码生成模型 Codestral-22B&#xff0c;宣称在多项指标上超越了 GPT-4 和 Llama3&…

计算机网络:应用层 - 万维网 HTTP协议

计算机网络&#xff1a;应用层 - 万维网 & HTTP协议 万维网 WWW统一资源定位符 URL 超文本传输协议 HTTP非持续连接持续连接非流水线流水线 代理服务器HTTP报文 万维网 WWW 万维网是一个大规模的、联机式的信息储藏所。万维网用链接的方法能非常方便地从互联网上的一个站点…

企业的差旅费用还能更节省吗?

对于多数企业而言&#xff0c;差旅成本是仅次于人力资源成本的第二大可控成本。 差旅成本除了差旅产品采购费用、差旅服务费用这些显性成本外&#xff0c;还有预订时间消耗、审批环节、报销流程、票据核查等隐性成本。 据调研数据显示&#xff1a;企业对于专业差旅管理的认知度…

测试服务器端口是否打开,服务器端口开放异常的解决方法

在进行服务器端口开放性的测试时&#xff0c;我们通常使用网络工具来验证目标端口是否响应特定的协议请求。常用的工具包括Telnet、Nmap、nc&#xff08;netcat&#xff09;等。这些工具可以通过发送TCP或UDP数据包到指定的IP地址和端口&#xff0c;然后分析返回的数据包&#…

「Python-docx 专栏」docx 获取页面大小、设置页面大小(纸张大小)

本文目录 前言一、docx纸张大小介绍1、document.xml① 关于 document.xml 的一些知识点② 纸张大小在哪里③ 纸张大小都有啥④ EMU对应的尺寸列表二、获取docx纸张大小1、完整代码2、运行效果图三、python为docx设置纸张大小1、完整代码2、效果图前言 今天的这边文章,我们来说…

DNS部署与安全

一、DNS 英文全称&#xff1a;Domain Name Service 含义&#xff1a;域名服务 作用&#xff1a;为客户机提供域名解析服务 二、域名组成 域名组成概述 &#xff08;1&#xff09;如"www.sina.com.cn”是一个域名&#xff0c;从严格意义上讲&#xff0c;“sina.com.cn”…

总结 CSS 选择器的常见用法

一&#xff0c;什么是css 在前端网页中&#xff0c;css就相当于化妆术&#xff0c;把一个很生硬的网页页面变得排版有序起来。 CSS可以对网页中的元素位置进行像素级精准控制&#xff0c;实现美化页面的效果&#xff0c;也能做到页面的样式和结构分离。 二&#xff0c;css的基…

前端下载文件流,axios设置responseType: arraybuffer/blob无效

项目中调用后端下载文件接口&#xff0c;设置responseType: arraybuffer,实际拿到的数据data是字符串 axios({method: post,url: /api/v1/records/recording-file/play,// 如果有需要发送的数据&#xff0c;可以放在这里data: { uuid: 06e7075d-4ce0-476f-88cb-87fb0a1b4844 }…

COSMOSPANDA星际熊猫闪耀助阵2023中国(广州)国际时尚产业大会

在2023年12月27日至12月29日于广州海心沙盛大举行的2023中国&#xff08;广州&#xff09;国际时尚产业大会上&#xff0c;备受瞩目的星际熊猫以其独特的IP精神与理念&#xff0c;成为本次活动的焦点。 打造“时尚之都”是提升广州国际知名度、消费繁荣度、商业活跃度的重要抓…

Python 基础:异常

目录 一、异常概念二、处理异常2.1 抛出异常2.2 使用 try-except 代码块2.3 使用 try-except-else 代码块2.4 静默失败 三、总结 遇到看不明白的地方&#xff0c;欢迎在评论中留言呐&#xff0c;一起讨论&#xff0c;一起进步&#xff01; 本文参考&#xff1a;《Python编程&a…

《A DECODER-ONLY FOUNDATION MODEL FOR TIME-SERIES FORECASTING》阅读总结

介绍了一个名为TimeFM的新型时间序列预测基础模型&#xff0c;该模型受启发于自然语言处理领域的大语言模型&#xff0c;通过再大规模真实世界和合成时间序列数据集上的预训练&#xff0c;能够在多种不同的公共数据集上实现接近最先进监督模型的零样本预测性能。 该模型使用真…

数据资产与用户体验优化:深入挖掘用户数据,精准分析用户需求与行为,优化产品与服务,提升用户体验与满意度,打造卓越的用户体验,赢得市场认可

一、引言 在数字化时代&#xff0c;数据已经成为企业最宝贵的资产之一。通过深入挖掘和分析用户数据&#xff0c;企业能够精准把握用户需求和行为&#xff0c;从而优化产品与服务&#xff0c;提升用户体验和满意度。这不仅有助于企业在激烈的市场竞争中脱颖而出&#xff0c;还…

人工智能系列:一文让你读懂什么是模式识别

目录 1.什么是模式识别 1.1人工智能和模式识别 1.2信息感知 1.3计算机模式识别 1.4模式识别应用 1.5模式识别发展简史 1.6相关问题和领域 2.模式识别形式化 2.1模式和模式识别 2.2模式表示 2.3特征空间 2.4特征空间中的分类 2.5一个例子 3.模式识别系统流程 4.模…

【维护服务器安全,如何应对恶意的威胁行为?】

随着互联网的迅猛发展&#xff0c;网络服务器成为现代社会中不可或缺的基础设施。然而&#xff0c;恶意攻击行为也日益猖獗&#xff0c;技术不断升级&#xff0c;给网络服务器的安全带来了严峻挑战。下面德迅云安全就分享一些常见的危害服务器安全的行为&#xff0c;和相应的应…

某棋牌渗透测试

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、信息收集 这里通过fofa进行收集&#xff0c;语法为&#xff1a;body某棋牌 && titlexxx 图1-1 fofa资产收集 …

JetBrains IDEA 新旧UI切换

JetBrains IDE 新旧UI切换 IntelliJ IDEA 的老 UI 以其经典的布局和稳定的性能&#xff0c;成为了许多开发者的首选。而新 UI 则在此基础上进行了全面的改进&#xff0c;带来了更加现代化、响应式和高效的用户体验。无论是新用户还是老用户&#xff0c;都可以通过了解和适应这…

apple watch上watchOS网络低级别和高级别区别,以及使用tcp/udp或者websocket的限制条件

可以直接看官方文档&#xff1a;TN3135: Low-level networking on watchOS | Apple Developer Documentation 高级网络&#xff1a;包括 URLSession 中的 HTTP 和 HTTPS 支持&#xff0c;以及在此之上的任何代码层。 低层网络&#xff1a;包括网络框架、 NSStream 和任何其他…

根据滚珠丝杆规格适配丝杆支撑座!

丝杆支撑座是机械设备中常见的支撑座装置&#xff0c;支撑着滚珠丝杆&#xff0c;起到稳定和支撑的作用&#xff0c;避免丝杆弯曲变形和震动&#xff0c;从而保证设备的稳定运行。那么&#xff0c;我们应该如何正确选择丝杆支撑座呢&#xff1f; 在选择丝杆支撑座时&#xff0c…

数据结构试题 16-17

先这样吧&#xff0c;&#xff0c;专业课不是统考&#xff0c;我发现每年的卷子风格都不太一样&#xff0c;侧重点也不一样。以及21的和16的发生了很大的改变。等明年1月再看看吧 那就先over啦 数据结构撒花&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…

PTA - 函数的定义与调用

编写一个名为collatz()的函数,它有一个名为number的参数&#xff1a; 如果number是偶数,那么collatz()就打印number加上2如果number是奇数,那么collatz()就打印number乘以2 函数接口定义&#xff1a; def collatz(number)裁判测试程序样例&#xff1a; /* 请在这里填写答案…