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

文章目录

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

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

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

相关文章

COCO格式转YOLO格式训练

之前就转换过好几次&#xff0c;每次换设备训练&#xff0c;由于压缩包太大&#xff0c;u盘不够用。每次都要找教程从网上再下载一遍。因此这里记录一下&#xff0c;以免下次重新找教程。 在coco数据集中&#xff0c;coco2017train或coco2017val数据集中标注的目标(类别)位置在…

Qt事件学习案例

视频链接 https://www.bilibili.com/video/BV18B4y1K7Cs?p7&spm_id_frompageDriver&vd_sourcefa4ef8f26ae084f9b5f70a5f87e9e41bQt5跟着视频做即可&#xff0c;Qt6部分代码需要改动,改动的地方注释有写 素材 百度云 链接&#xff1a;https://pan.baidu.com/s/158j…

金三银四面试题(十四):Java基础问题(5)

这部分面试题多用于面试的热身运动&#xff0c;对很多找实习和准备毕业找工作的小伙伴至关重要。 避免序列化 可以使用transient 关键字修饰不想进行序列化的变量。 transient 关键字的作用是&#xff1a;阻止实例中那些用此关键字修饰的变量序列化&#xff1b;当对象被反序列…

Python 网络请求:深入理解Requests库

目录 引言 一、Requests库简介 二、安装与基本使用 三、requests库的特性与优势 四、requests库在实际应用中的案例 1.get请求 2.post请求 3.超时重试 4.headers设置 5.session会话 6.携带cookie​​​​​​​ 7.携带代理​​​​​​​ 8.携带身份认证​​​​​…

Windows集群部署项目

目录 一&#xff0c;环境准备 1.1.安装MySQL 1.2.安装JDK 1.3.安装TomCat 1.4.安装Nginx 二&#xff0c;部署 2.1.后台服务部署 2.2.Nginx配置负载均衡及静态资源部署 一&#xff0c;环境准备 1.1.安装MySQL 可以参考博客&#xff1a;http://t.csdnimg.cn/A75bg 1.2.…

FPGA(Verilog)实现uart传输协议传输数据(含仿真)

实现功能&#xff1a; 1.接收uart串行数据&#xff0c;输出并行数据(1byte)。 2.输入并行数据(1byte)&#xff0c;输出uart串行数据。 3.完成uart传输的1次环回。 uart协议的1帧数据传输 uart_test系统框图 Verilog代码实现 1.uart接收模块:接收串行数据,输出并行数据和其有…

72小时内报告!美国发布关键基础设施网络攻击通报新规草案

美国网络安全和基础设施安全局(CISA)本周四发布了关键基础设施企业如何向政府报告网络攻击的规定草案。 新规基于拜登2022年3月15日签署的美国《关键基础设施网络事件报告法案》(简称CIRCIA)。这是美国联邦政府首次提出一套跨关键基础设施部门的全面网络安全规则。CISA正在就规…

计算机网络-HTTP相关知识-基础

HTTP基础 基本概念&#xff1a;HTTP是一种计算机之间交流通信的规范&#xff0c;它允许数据在两点之间传输&#xff0c;这个过程可以包括中转或接力。HTTP不仅仅包括文本&#xff0c;还可以包括图片、音频等超文本。状态码&#xff1a;HTTP状态码分为五类&#xff1a; 2xx&…

intellij idea 使用git撤销(取消)commit

git撤销(取消) 未 push的 commit Git&#xff0c;选择分支后&#xff0c;右键 Undo Commit &#xff0c;会把这个 commit 撤销。 git撤销(取消) 已经 push 的 commit 备份分支内容&#xff1a; 选中分支&#xff0c; 新建 分支&#xff0c;避免后续因为操作不当&#xff0c;导…

windows版本-idea中下载的java版本在哪

1、点击idea的file-projectStructure 进入&#xff1a; 通过电脑目录进入该目录 找到bin目录&#xff0c;copy该目录地址 copy下来之后设置到系统环境变量中

经济学 博弈论 行为经济学

四种市场结构&#xff1a; 划分依据&#xff1a;生产者的数量&#xff0c;对价格的控制力&#xff0c;进入市场的难度&#xff08;新的商家进入市场的困难难度&#xff09; 1.完全竞争市场&#xff08;大多数农业产品&#xff1a;草莓&#xff09; 个体商家对价格没有控制力&a…

Android屏幕自适应

Android屏幕自适应 Android屏幕适配出现的原因为什么Android需要进行屏幕适配&#xff1f; 屏幕基本概念屏幕尺寸屏幕分辨率和像素sppxdp 密度无关像素dpiDensity 屏幕方向横屏竖屏自动切换禁用自动切换屏幕方向 Android屏幕自适应1. dp原生方案2. 线形布局权重示例代码 3. Jav…

阿里云短信服务业务

一、了解阿里云用户权限操作 1.注册账号、实名认证&#xff1b; 2.使用AccessKey 步骤一 点击头像&#xff0c;权限安全的AccessKey 步骤二 设置子用户AccessKey 步骤三 添加用户组和用户 步骤四 添加用户组记得绑定短信服务权限 步骤五 添加用户记得勾选openApi访问 添加…

Higgsfield AI: 对飙Sora打造个性化视频新浪潮,重塑社交媒体内容创作

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

对于个人如何选择服务器是最适合的?

选择适合的服务器对于个人来说是一个重要的决策&#xff0c;因为它会直接影响到你的网站或应用的性能、稳定性和成本。以下是一些建议&#xff0c;帮助你选择最适合的服务器&#xff1a; 京东云服务器&#xff0c;一年2G2H3M只需要50元&#xff01;&#xff01; 进入活动会场…

isc-dhcp-server DNS配置

我遇到一个有趣的问题&#xff0c;我先在一台Ubuntu服务器上使用isc-dhcp-server在其其中一个网口运行DHCP服务&#xff0c;然后我自己的笔记本电脑直连到这个网口&#xff0c;来上网。 本来直接就应该能上网&#xff0c;但是我的电脑只有在打开Clash时才能访问互联网&#xf…

【御控物联】JavaScript JSON结构转换(18):数组To对象——多层属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON数组 To JSON对象》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

猫头虎分享已解决Bug: ERROR: Could not find a version that satisfies the requirement

猫头虎分享已解决Bug: ERROR: Could not find a version that satisfies the requirement &#x1f42f;&#x1f4bb; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎博主&#xff0c;今天我们要聊聊后端技术领域中的一个常见Bug&#xff1a;ERROR: Could not find a vers…

Linux第3课 Linux系统安装及换源方法

文章目录 Linux第3课 Linux系统安装及换源方法一、VMware虚拟机下系统的安装及配置&#xff08;一&#xff09;创建新的虚拟机 二、换源三、初次配置四、修改分辨率五、共享文件夹的实现&#xff08;一&#xff09;创建并查看共享文件夹 Linux第3课 Linux系统安装及换源方法 用…

java生成word

两种方案 一、poi-tl生成word <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.1</version> </dependency> public static void main(String[] args) throws Exception {String…