DAY15|二叉树Part03|LeetCode: 513.找树左下角的值、112. 路径总和、106. 从中序与后序遍历序列构造二叉树

LeetCode: 513.找树左下角的值

力扣代码链接

文字讲解:LeetCode: 513.找树左下角的值

视频讲解:怎么找二叉树的左下角? 递归中又带回溯了,怎么办?

基本思路

        对题目进行一下分析,要找二叉树最底层最左边节点的数值。首先要是最后一行,然后是最左边的值。如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行。

        那么如何找最左边的呢?使用层序遍历当然是可以的,而如果使用递归的方法可以使用前序遍历(当然中序,后序都可以,因为本题没有中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。

        注意:有一个地方容易误解,即二叉树最底层最左边的节点不一定是左孩子,也可能是右孩子!!!

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

        首先需要遍历根节点,以及定义一个变量记录深度为int类型,返回值可以为void。本题还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。

int maxDepth = INT_MIN;   // 全局变量 记录最大深度
int result;       // 全局变量 最大深度最左节点的数值
void traversal(TreeNode* root, int depth)
  • 确定终止条件

        当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。

if (root->left == NULL && root->right == NULL) {if (depth > maxDepth) {maxDepth = depth;           // 更新最大深度result = root->val;   // 最大深度最左面的数值}return;
}
  • 确定单层递归的逻辑

        在找最大深度的时候,递归的过程中依然要使用回溯。

if (root->left) {   // 左depth++; // 深度加一traversal(root->left, depth);depth--; // 回溯,深度减一
}
if (root->right) { // 右depth++; // 深度加一traversal(root->right, depth);depth--; // 回溯,深度减一
}
return;

C++代码

class Solution {
public:int maxDepth = INT_MIN;int result;void traversal(TreeNode* root, int depth) {if (root->left == NULL && root->right == NULL) {if (depth > maxDepth) {maxDepth = depth;result = root->val;}return;}if (root->left) {depth++;traversal(root->left, depth);depth--; // 回溯}if (root->right) {depth++;traversal(root->right, depth);depth--; // 回溯}return;}int findBottomLeftValue(TreeNode* root) {traversal(root, 0);return result;}
};

LeetCode: 112. 路径总和、113.路径总和II

力扣代码链接:112. 路径总和、113.路径总和II

文字讲解:LeetCode: 112. 路径总和、113.路径总和II

视频讲解:拿不准的遍历顺序,搞不清的回溯过程,我太难了!

LeetCode: 112. 路径总和

        这道题我们要遍历从根节点到叶子节点的路径看看总和是不是目标和。可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树。

        如下图所示,这里明显要采用回溯算法,如果当前路径不符合,我们就从倒数第二天路还是回溯,还是不行就回溯倒数第三、倒数第四条路,直到找到合适的路径或者全部搜索完成。

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

        参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

        返回值:我们应该是只要找一条符合条件的路径,递归函数就需要及时返回,那么返回类型是bool。

bool traversal(treenode* cur, int count)   // 注意函数的返回类型
  • 确定终止条件

        不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。如果遍历到了叶子节点,count不为0,就是没找到。

if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
  • 确定单层递归的逻辑

        因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

if (cur->left) { // 左count -= cur->left->val; // 递归,处理节点;if (traversal(cur->left, count)) return true;count += cur->left->val; // 回溯,撤销处理结果
}
if (cur->right) { // 右count -= cur->right->val;if (traversal(cur->right, count)) return true;count += cur->right->val;
}
return false;

C++代码

class Solution {
private:bool traversal(TreeNode* cur, int count) {if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回if (cur->left) { // 左count -= cur->left->val; // 递归,处理节点;if (traversal(cur->left, count)) return true;count += cur->left->val; // 回溯,撤销处理结果}if (cur->right) { // 右count -= cur->right->val; // 递归,处理节点;if (traversal(cur->right, count)) return true;count += cur->right->val; // 回溯,撤销处理结果}return false;}public:bool hasPathSum(TreeNode* root, int sum) {if (root == NULL) return false;return traversal(root, sum - root->val);}
};

LeetCode: 113.路径总和II

基本思路

113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!

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

参数:依旧需要遍历所有的根节点,并且使用count记录目标和。

返回值:返回值为void,因为我们要遍历整个树,然后记录路径,所以递归函数要不返回值

注意:本题要求我们求出整条路径,因此我们需要一个数组记录所有路径,另外还需要一个数组记录所有满足条件的路径。

vector<vector<int>> result;
vector<int> path;
void traversal(TreeNode* cur, int count)
  • 确定终止条件

确定终止条件,直接跟之前一样即可

if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径result.push_back(path);return;
}
if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回
  • 确定单层递归的逻辑
if (cur->left) { // 左 (空节点不遍历)path.push_back(cur->left->val);count -= cur->left->val;traversal(cur->left, count);    // 递归count += cur->left->val;        // 回溯path.pop_back();                // 回溯
}
if (cur->right) { // 右 (空节点不遍历)path.push_back(cur->right->val);count -= cur->right->val;traversal(cur->right, count);   // 递归count += cur->right->val;       // 回溯path.pop_back();                // 回溯
}
return ;

C++代码

class solution {
private:vector<vector<int>> result;vector<int> path;// 递归函数不需要返回值,因为我们要遍历整个树void traversal(TreeNode* cur, int count) {if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径result.push_back(path);return;}if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回if (cur->left) { // 左 (空节点不遍历)path.push_back(cur->left->val);count -= cur->left->val;traversal(cur->left, count);    // 递归count += cur->left->val;        // 回溯path.pop_back();                // 回溯}if (cur->right) { // 右 (空节点不遍历)path.push_back(cur->right->val);count -= cur->right->val;traversal(cur->right, count);   // 递归count += cur->right->val;       // 回溯path.pop_back();                // 回溯}return ;}public:vector<vector<int>> pathSum(TreeNode* root, int sum) {result.clear();path.clear();if (root == NULL) return result;path.push_back(root->val); // 把根节点放进路径traversal(root, sum - root->val);return result;}
};

LeetCode: 106. 从中序与后序遍历序列构造二叉树

力扣代码链接

文字讲解:LeetCode: 106. 从中序与后序遍历序列构造二叉树

视频讲解:坑很多!来看看你掉过几次坑

基本思路

        首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

把步骤量化出来:

(第一步:如果数组大小为零的话,说明是空节点了。)
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间

可以根据步骤写出整体框架:

TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {// 第一步if (postorder.size() == 0) return NULL;// 第二步:后序遍历数组最后一个元素,就是当前的中间节点int rootValue = postorder[postorder.size() - 1];TreeNode* root = new TreeNode(rootValue);// 叶子节点if (postorder.size() == 1) return root;// 第三步:找切割点int delimiterIndex;for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;}// 第四步:切割中序数组,得到 中序左数组和中序右数组// 第五步:切割后序数组,得到 后序左数组和后序右数组// 第六步root->left = traversal(中序左数组, 后序左数组);root->right = traversal(中序右数组, 后序右数组);return root;
}

难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。

关于切割我们应该遵循数组部分讲到的循环不变量原则,以下统一按照左闭右开区间进行切割。

另外切割时,首先要切割中序数组,因为中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割。

// 找到中序遍历的切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;
}// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );

接下来就要切割后序数组了。首先后序数组的最后一个元素指定不能要了,这是切割点 也是 当前二叉树中间节点的元素,已经用了。

后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

// postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
postorder.resize(postorder.size() - 1);// 左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

接下来就是开始递归:

root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);

C++代码

class Solution {
private:TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {if (postorder.size() == 0) return NULL;// 后序遍历数组最后一个元素,就是当前的中间节点int rootValue = postorder[postorder.size() - 1];TreeNode* root = new TreeNode(rootValue);// 叶子节点if (postorder.size() == 1) return root;// 找到中序遍历的切割点int delimiterIndex;for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;}// 切割中序数组// 左闭右开区间:[0, delimiterIndex)vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);// [delimiterIndex + 1, end)vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );// postorder 舍弃末尾元素postorder.resize(postorder.size() - 1);// 切割后序数组// 依然左闭右开,注意这里使用了左中序数组大小作为切割点// [0, leftInorder.size)vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());// [leftInorder.size(), end)vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());root->left = traversal(leftInorder, leftPostorder);root->right = traversal(rightInorder, rightPostorder);return root;}
public:TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {if (inorder.size() == 0 || postorder.size() == 0) return NULL;return traversal(inorder, postorder);}
};

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

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

相关文章

ClkLog企业版(CDP)预售开启,更有鸿蒙SDK前来助力

新版本发布 ClkLog在上线近1年后&#xff0c;获得了客户的一致肯定与好评&#xff0c;并收到了不少客户对功能需求的反馈。根据客户的反馈&#xff0c;我们在今年三季度对ClkLog的版本进行了重新的规划与调整&#xff0c;简化了原有的版本类型&#xff0c;方便客户进行选择。 与…

C++:set和map的使用

目录 序列式容器和关联式容器 set set类的介绍 构造和迭代器 增删查 insert find和erase erase迭代器失效 lower_bound与upper_bound multiset和set的区别 map map类的介绍 pair类型介绍 构造和迭代器 增删查 map数据修改&#xff1a;重载operator[] multimap…

Unix和Linux系统中的文件权限

详细解释Unix和Linux系统中的文件权限设置以及如何使用chmod命令来修改这些权限。 文件权限的详细解释 在Unix和Linux系统中&#xff0c;文件权限是控制谁可以访问和操作文件或目录的重要机制。权限分为三类&#xff1a;所有者&#xff08;owner&#xff09;、所属组&#xf…

android定时器循环实现轮播图

说明&#xff1a; android定时器加for循环实现轮播图 效果&#xff1a; step1: package com.example.iosdialogdemo;import android.os.Bundle; import android.os.Handler; import android.widget.ImageView; import android.widget.TextView;import androidx.appcompat.ap…

ChatGPT能预测时间序列?基于大模型的时间序列预测中的迭代事件推理_chatgpt能预测时间序列

引言 时间序列预测&#xff08;Time Series Forecasting&#xff09;是支撑经济、基础设施和社会各领域决策的关键技术。然而&#xff0c;传统的预测方法在面对由外部随机事件引起的突发性变化或异常时&#xff0c;往往表现出局限性。这些方法通常依赖于历史数据的模式识别&am…

计算机网络-传输层提供的服务

传输层在协议栈中的位置 我们可以给应用层的这些应用程序提供我们想要传输的数据&#xff0c;比如说我们想用微信传一张图片&#xff0c;或者想用QQ发一串字符。那这些数据是由我们用户直接提供的&#xff0c;那么我们的数据交给了应用层的某一个进程之后。这个进程可能会在我们…

将Notepad++添加到右键菜单【一招实现】

一键添加注册表 复制以下代码保存为 Notepad.reg&#xff0c;将红框内路径修改为自己电脑的“Notepad.exe路径”后&#xff0c;再双击运行即可。 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\NotePad] "Notepad" "Icon""D:\\N…

存储引擎技术进化

B-tree 目前支撑着数据库产业的半壁江山。 50 年来不变而且人们还没有改变它的意向 鉴定一个算法的优劣&#xff0c;有一个学派叫 IO复杂度分析 &#xff0c;简单推演真假便知。 下面就用此法分析下 B-tree(traditional b-tree) 的 IO 复杂度&#xff0c;对读、写 IO 一目了…

vscode | 开发神器vscode快捷键删除和恢复

目录 快捷键不好使了删除快捷键恢复删除的快捷键 在vscode使用的过程中&#xff0c;随着我们自身需求的不断变化&#xff0c;安装的插件将会持续增长&#xff0c;那么随之而来的就会带来一个问题&#xff1a;插件的快捷键重复。快捷键重复导致的问题就是快捷键不好使了&#xf…

00后整顿职场!一个人的底层逻辑,就是他的命运——早读(逆天打工人爬取热门微信文章解读)

00后整顿职场 引言Python 代码第一篇 洞见 一个人的底层逻辑&#xff0c;就是他的命运第二篇 百股跌停结尾 想看爽文情节的后续 引言 昨天晚上基本悟了 空空如也 之前听过一句话 非有之有为秒有 非空之空为真空 大致意思是 事物虽然没有独立不变的自性&#xff08;非有&#…

NR cell配置SUL时,RA要在哪个carrier进行?

当一个NR小区配置了 SUL 后&#xff0c;其接入所需参数&#xff0c;包括 SUL 的频段、PointA、SCS 子载波间隔&#xff0c;带宽等&#xff0c;会通过 SIB1 下发给UE。 如上图所述&#xff0c;配置有SUL的小区进行RA时&#xff0c;网络可以明确告知UE使用SUL还是UL。例如通过PDC…

安全成为大模型的核心;大模型安全的途径:大模型对齐

目录 安全成为大模型的核心 大模型安全的途径:大模型对齐 人类反馈强化学习(RLHF) 直接偏好优化(DPO) 安全成为大模型的核心 大模型安全的途径:大模型对齐 大模型对齐技术(Alignment Techniques for Large Language Models)是确保大规模语言模型(例如GPT-4)的输…

视频一键转换3D:Autodesk 发布 Video to 3D Scene

Video 3D Scene 最近 Autodesk 旗下公司 Wonder Dynamics 推出了 Wonder Animation 的测试版&#xff0c;它使用突破性的视频到 3D 场景技术&#xff0c;通过将任何视频序列转换为 3D 动画场景来加速动画电影的制作。 Video 3D Scene Video 3D Scene 生成效果 作为 Wonder Stud…

《数字图像处理基础》学习03-图像的采样

在之前的学习中我已经知道了图像的分类&#xff1a;物理图像和虚拟图像。《数字图像处理基础》学习01-数字图像处理的相关基础知识_图像处理 数字-CSDN博客 目录 一&#xff0c;连续图像和离散图像的概念 二&#xff0c;图像的采样 1&#xff0c; 不同采样频率采样同一张图…

【MongoDB】Windows/Docker 下载安装,MongoDB Compass的基本使用、NoSQL、MongoDB的基础概念及基础用法(超详细)

文章目录 Windows下载MongoDB Compass使用NoSQL的基本概念MongoDB常用术语MongoDB与RDBMS区别MongoDB的CRUD 更多相关内容可查看 Docker安装MongoDB可查看&#xff1a;Docker-安装MongoDB Windows下载 官网下载地址&#xff1a;https://www.mongodb.com/try/download/communi…

二 MyBatis入门程序

二、MyBatis入门程序 2.1 MyBatis入门程序开发步骤 写代码前准备&#xff1a; 准备数据库表&#xff1a;汽车表t_car&#xff0c;字段包括&#xff1a; id&#xff1a;主键&#xff08;自增&#xff09;【bigint】car_num&#xff1a;汽车编号【varchar】brand&#xff1a;品牌…

网络自动化02:基于xlsx传入设备信息与所需执行备份配置命令,使用netmiko自动化登录分发

这是这个系列第二篇 本文将仅简单介绍使用xlsx导入设备信息&#xff0c;并使用netmiko配置 目录 环境设备信息表格式单线程代码解释代码逻辑分析函数解析 逻辑调用图逻辑说明 遇到的问题写在最后 环境 所有设备均能与我执行Python脚本的环境互通&#xff0c;同时均拥有独立的ip…

自动化测试类型与持续集成频率的关系

持续集成是敏捷开发的一个重要实践&#xff0c;可是究竟多频繁的集成才算“持续”集成&#xff1f; 一般来说&#xff0c;持续集成有3种常见的集成频率&#xff0c;分别是每分钟集成、每天集成和每迭代集成。项目组应当以怎样的频率进行集成&#xff0c;这取决于测试策略&…

制作一个简易恒流电子负载教程,实战教程,单片机程序,电路图,方案

师从&#xff1a;https://www.bilibili.com/read/cv4088568/ 题目是这样的&#xff1a; 设计和制作一台恒流&#xff08;CC&#xff09;工作模式的简易直流电子负载。其原理示意图如图1所示。 图1简易直流电子负载原理示意图 二、要求 1.基本要求 &#xff08;1&#xff09;…

日期类数据和控件管理

目录 QTime API QDate API QDateTime QDateTimeEdit API QCalendarWidget API 测试样例&#xff08;参考了《Qt6开发指南》&#xff09; (严肃提示&#xff1a;笔者认为这里的控件没有难点&#xff0c;本篇文章实际上是GPT辅助的代码整理和表格使用&#xff0c;笔者建…