代码随想录算法训练营DAY17|C++二叉树Part.4|110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

news2024/11/24 23:18:13

文章目录

  • 110.平衡二叉树
    • 思路
    • 伪代码
    • CPP代码
  • 257.二叉树的所有路径
    • 思路
    • 伪代码实现
    • CPP代码
  • 404.左叶子之和
    • 思路
    • 伪代码
    • CPP代码

110.平衡二叉树

力扣题目链接

文章讲解:110.平衡二叉树

视频讲解:后序遍历求高度,高度判断是否平衡 | LeetCode:110.平衡二叉树

状态:第一眼要用后序遍历,因为对于某结点我们需要收集其左右孩子的信息。

思路

本题中,平衡二叉树的定义就是:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1

并且既然是要求比较高度,必然要使用后序遍历。

这里的递归函数,通过返回二叉树的高度是最好的,即使我们不需要求二叉树的高度。因为在计算高度的过程中,我们才好去判断左右子树的高度差。

所以一定要注意的就是我们的递归函数代码本质上是求二叉树的高度,而不是直接去判断是否是平衡二叉树。


还有一个一定要注意的情况:
在这里插入图片描述
该二叉树不是平衡二叉树!因为左结点的2,其左孩子高度为2,右孩子高度为0!所以不是平衡二叉树,在代码中一定要考虑这种情况!

伪代码

  • 确定递归函数的参数和返回值:

    参数:当前结点;返回值:应当是以当前结点为根结点的树的高度。

// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
int getHeight(TreeNode* node)
  • 明确终止条件:递归的过程中依然是遇到空结点了为终止,返回0,表示当前结点为根结点的树的高度为0
if (node == NULL)
  return 0
  • 确定单层递归逻辑:

如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值

分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

int leftHeight = getHeight(node->left); //左
if (leftHeight == -1) return -1;		//思考思考这行代码的作用
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;		//同上

int result;
if (abs(leftHeight - rightHeight) <= 1)
  result = 1 + max(leftHeight, rightHeight);
else
  return -1;

return result;

代码if (leftHeight == -1) return -1这行代码一定要写上,因为他们也同样是在求高度,如果该根结点左子树的高度已经返回了-1,那么这颗树肯定不是平衡二叉树!

  • 单层递归逻辑精简后的代码:
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);

CPP代码

class Solution {
public:
    // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
    int getHeight(TreeNode* node) {
        if (node == NULL) {
            return 0;
        }
        int leftHeight = getHeight(node->left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;
        return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
    }
    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};

257.二叉树的所有路径

力扣题目链接

文章讲解:257.二叉树的所有路径

视频讲解:递归中带着回溯,你感受到了没?| LeetCode:257. 二叉树的所有路径

状态:只知道用前序遍历,剩下的代码特别是关于单层递归逻辑那里根本就写不出来,如何处理遇到分叉路口的情况呢?(即回溯逻辑) 还有一个要注意的点就是关于递归终止条件,本题的终止条件和之前的有什么不一样呢?

思路

本题其实就是求二叉树的遍历路径,那么我们就一定要保存从根结点开始遍历的路径。

很明显,要使用前序遍历。并且在本题中要有回溯的逻辑。

20210204151702443

伪代码实现

  • 确定递归函数的参数和返回值:我们传入的参数就包含了要记录结果的数组,所以返回值肯定是void,至于参数包括传入根节点,记录每一条路径的path,和存放结果集的result
void traversal(TreeNode* node, vector<string>& result, vector<int>& path){
} 
  • 确定递归终止条件:之前的题目都是遍历到空结点即可终止递归;但是在本题中,我们是要寻找到叶子结点(防止左叶子为空,右叶子不为空的情况)才能终止。因为我们本质上要确定是叶子结点的时候,才能真正的结束。而且由于我们对最后遍历的叶子结点要进行操作,所以返回的地方一定要是该叶子结点,而不能是 node == NULL 的时候。
if (node == NULL) return;//No!
if (node->left == NULL && node->right == NULL) {
	处理逻辑
}

为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。

再来看一下终止处理的逻辑:这里使用vector 结构path来记录路径,所以要把vector 结构的path转为string格式,再把这个string 放进 result里。

那么为什么使用了vector 结构来记录路径呢?

因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。

if (node->left == NULL && node->right == NULL) {	//遇到了叶子结点
	string sPath;
  	for (int i = 0; i < path.size() - 1; i++){	//将path里记录的路径转换为string格式
    sPath += to_string(path[i]);
    sPath += "->";
  }
  sPath += to_string(path[path.size() - 1]);	//记录当前叶子结点
  result.push_back(sPath);	//收集一个路径
  return;
} 
  • 确定单层递归逻辑:上面说过没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了

    • 上面的代码写到遍历到叶子结点我们就终止循环,那么什么时候把叶子结点压入path中呢?写到终止条件的前一步即可
    • 单层递归逻辑里面最难的其实就是如何进行回溯?其实就是对**path中的结点进行删除,然后加入新结点**。所以在代码中回溯和递归一定要一一对应,有一个递归就要有一个回溯。所以下面的代码逻辑是错的:
    if (cur->left) {
        traversal(cur->left, path, result);
    }
    if (cur->right) {
        traversal(cur->right, path, result);
    }
    path.pop_back();
    //这么写相当于把递归和回溯拆开了,一个在花括号里,一个在花括号外。
    
    //正确写法
    path.push_back(cur->val);	//中
    
    if (cur->left) {					//左
        traversal(cur->left, path, result);
        path.pop_back(); // 回溯
    }
    if (cur->right) {					//右
        traversal(cur->right, path, result);
        path.pop_back(); // 回溯
    }
    

CPP代码

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

精简版代码:


//精简版代码隐藏了回溯过程
class Solution {
private:

    void traversal(TreeNode* cur, string path, vector<string>& result) {
        path += to_string(cur->val); // 中
        if (cur->left == NULL && cur->right == NULL) {
            result.push_back(path);
            return;
        }
        if (cur->left) traversal(cur->left, path + "->", result); // 左
        if (cur->right) traversal(cur->right, path + "->", result); // 右
    }

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

    }
};

注意在函数定义的时候void traversal(TreeNode* cur, string path, vector<string>& result) ,定义的是string path,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。(这里涉及到C++语法知识)

那么在如上代码中,貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->" 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。

404.左叶子之和

力扣题目链接

文章讲解:404.左叶子之和

视频讲解:二叉树的题目中,总有一些规则让你找不到北 | LeetCode:404.左叶子之和

状态:还是有思路,我们的cur遍历到叶子结点的前一个结点,也就是cur结点的左结点的(左结点和右结点)为空,这样我们就能顺利找到左叶子。

思路

思路主要就是状态栏中的思路,主要是注意代码细节,代码实现个人感觉还是比较有难度的。

伪代码

  • 确定递归的返回值和参数:判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int

    使用题目中给出的函数就可以了。

    int getSum(TreeNode* root){
    }
    
  • 确定递归的终止条件:遍历到空结点,左叶子值为0;当前遍历到叶子结点,左叶子值仍然为0

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;

CPP代码

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

//精简后代码如下
class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == NULL) return 0;
        int leftValue = 0;
        if (root->left != NULL && root->left->left == NULL && root->left->right == NULL) {
            leftValue = root->left->val;
        }
        return leftValue + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
    }
};

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

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

相关文章

CSS导读 (复合选择器)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 二、CSS的复合选择器 2.1 什么是复合选择器 2.2 后代选择器(重要) 2.3 子选择器(重要) Questions 小提…

七年老测试整理的RF框架大全,一看就会,一学就懂

1.RF框架 全称robot framework,一个基于python开发的&#xff0c;自动化测试框架&#xff0c;这个框架可以做&#xff1a;web自动化&#xff0c;接口自动化&#xff0c;APP自动化。 github官网 1&#xff09;.安装python 检查python环境 python -V或 pip -V 2&#xff09;.…

【linux基础】bash脚本的学习:定义变量及引用变量、统计目标目录下所有文件行数、列数

假设目的&#xff1a;统计并输出指定文件夹下所有文件行数 单个文件可以用 wc -l &#xff1b;多个文件&#xff0c;可以用通配符 / 借助bash脚本 1.定义变量名&#xff0c;使用引号 a"bestqc.com.map" b"Anno.variant_function" c"enrichment/GOe…

UE4_导入内容_Alembic文件导入器

Alembic文件导入器 Alembic文件格式(.abc)是一个开放的计算机图形交换框架&#xff0c;它将复杂的动画化场景浓缩成一组非过程式的、与应用程序无关的烘焙几何结果。虚幻引擎4(UE4)允许你通过 Alembic导入器 导入你的Alembic文件&#xff0c;这让你可以在外部自由地创建复杂的…

android支付宝接入流程

接入前准备 接入APP支付能力前&#xff0c;开发者需要完成以下前置步骤。 本文档展示了如何从零开始&#xff0c;使用支付宝开放平台服务端 SDK 快速接入App支付产品&#xff0c;完成与支付宝对接的部分。 第一步&#xff1a;创建应用并获取APPID 要在您的应用中接入支付宝…

Hot100【十一】:编辑距离

// 定义dp[i][j]: 表示word1前i个字符转换到word2前j个字符最小操作数 // 初始化dp[m1][n1] class Solution {public int minDistance(String word1, String word2) {int m word1.length();int n word2.length();// 1. dp数组int[][] dp new int[m 1][n 1];// 2. dp数组初…

代码算法训练营day14 | 理论基础、递归遍历

day14&#xff1a; 理论基础二叉树的分类&#xff1a;二叉树的种类&#xff1a;满二叉树完全二叉树二叉搜索树平衡二叉搜索树 二叉树的存储方式&#xff1a;链式存储顺序存储 二叉树的遍历方式&#xff1a;深度优先和广度优先遍历实现方式 二叉树的定义&#xff1a; 递归遍历递…

【攻防世界】web2(逆向解密)

进入题目环境&#xff0c;查看页面信息&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;…

磁盘管理与文件管理

文章目录 一、磁盘结构二、MBR与磁盘分区分区的优势与缺点分区的方式文件系统分区工具挂载与解挂载 一、磁盘结构 1.硬盘结构 硬盘分类&#xff1a; 1.机械硬盘&#xff1a;靠磁头转动找数据 慢 便宜 2.固态硬盘&#xff1a;靠芯片去找数据 快 贵 硬盘的数据结构&#xff1a;…

重温OKHTTP源码

本文基于OkHttp4.12.0源码分析 官方地址 概括 本篇主要是对okhttp开源库的一个详细解析&#xff0c;包含详细的请求流程分析、各大拦截器的解读等。 使用方法 同步请求&#xff1a;创建一个OKHttpClient对象&#xff0c;一个Request对象&#xff0c;然后利用它们创建一个Ca…

动态代理

动态代理 动态代理和静态代理角色一致。 代理类是动态生成的&#xff0c;不是我们直接写好的。 动态代理分为俩大类&#xff1a;基于接口的动态代理、基于类的动态代理 基于接口&#xff1a;JDK动态代理&#xff08;以下示例就是这个&#xff09; 基于类&#xff1a;cglib jav…

微机原理——绪论

本篇文章是我在观看网课时记录的笔记。如有错误欢迎批评指正。 微机原理————绪论 我们在使用计算机时&#xff0c;最重要最核心的就是计算机的CPU(中央处理器)&#xff0c;决定了计算机的计算速度&#xff0c;但是CPU无法直接读取外界的温度、湿度、压力之类的物理量&…

MSTP/RSTP的保护功能

目录 原理概述 实验目的 实验内容 实验拓扑 1.配置RSTP/MSTP 2.配置BPDU保护 3.配置根保护 4.配置环路保护 5.配置TC-BPDU保护 原理概述 在RSTP或MSTP交换网络中&#xff0c;为了防止恶意攻击或临时环路的产生&#xff0c;可配置保护功能来增强网络的健壮性和安全性。…

VSCode配置AI自动补全插件Tabnine

面向软件开发人员的 AI 助手 使用 AI 代码完成更快地编写代码 什么是Tabnine Tabnine 是一款 AI 代码助手&#xff0c;可让您成为更好的开发人员。Tabnine 将通过所有最流行的编码语言和 IDE 的实时代码完成、聊天和代码生成来提高您的开发速度。 无论您将其称为 IntelliSens…

【网络捉鬼记】微信可以部分网页可以,其它网页打不开提示无法找到NDS地址

蹭网蹭得好好的&#xff0c;为啥突然这样呢&#xff1f; 发现微信可以&#xff0c;百度搜索网页可以打开但图片出不来&#xff0c;再点一个新闻进去又是上图的样子。 问AI&#xff01;却发现连质谱清言也打不开&#xff01;用自己热点问&#xff1a; 至于win10怎么更换DNS&…

免费幻兽帕鲁游戏云服务器领取及搭建教程

幻兽帕鲁是一款多人在线游戏&#xff0c;为了获得更好的游戏体验&#xff0c;许多玩家会选择自行搭建游戏联机服务器&#xff0c;但是游戏云服务器一般配置较高&#xff0c;价格自然也比较高&#xff0c;本文将为大家分享免费幻兽帕鲁游戏云服务器领取及搭建教程。 雨云是一家国…

16.事件标志组

一、简介 事件标志组与信号量一样属于任务间同步的机制&#xff0c;但是信号量一般用于任务间的单事件同 步&#xff0c;对于任务间的多事件同步&#xff0c;仅使用信号量就显得力不从心了。FreeRTOS 提供的事件标志组 可以很好的处理多事件情况下的任务同步。 1. 事件标志 …

C语言文件操作2

1.二进制读写函数 在上一章我们介绍了字符读写函数、文本读写函数和格式化输入输出函数&#xff0c;这张我们继续为大家介绍剩下的一组读写函数——二进制读写函数&#xff1a;fread函数和fwrite函数。 ⚀fread函数 &#x1f7e1;函数作用 以二进制的方式从指定流中读取数据 …

Nuxt3 实战 (三):使用 release-it 自动管理版本号和生成 CHANGELOG

release-it 能做什么&#xff1f; 增加版本号并提交 Git生成变更日志&#xff08;Changelog&#xff09;并提交到 Git创建 Git 标签并推送到远程仓库发布到 npm 等软件仓库在 GitHub、GitLab 等平台创建发行版 前置知识 在看这篇文章之前&#xff0c;我们有必要了解一下 Sem…

Java 那些诗一般的 数据类型 (下篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能接…