Studying-代码随想录训练营day16| 513找到左下角的值、112.路径总和、106从中序与后序遍历序列构造二叉树

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

目录

513找到左下角的值

112.路径总和

113.路径总和II

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

105.从前序与中序遍历序列构造二叉树 

总结 


513找到左下角的值

文档讲解:代码随想录找到左下角的值

视频讲解:手撕找到左下角的值

题目:

学习:注意是找到最底层最左边的值,而不是找到最左边的左节点。两者是有很大差别的,对于第二个示例就能看出,并且最底层最左边的值也未必是左节点,如果示例2中4有一个右节点,那最底层最左边的值就是4的右节点了。

代码:因此本题采用层序遍历最好理解,每次从左到右遍历,记入每次遍历的第一个节点,就是该层最左边的节点,直到找到最后一层。

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:int findBottomLeftValue(TreeNode* root) {queue<TreeNode*> que;if(root != nullptr) que.push(root);int result;while(!que.empty()) {int size = que.size();result = que.front()->val;while(size--) {TreeNode* node = que.front();que.pop();if(node->left) que.push(node->left);if(node->right) que.push(node->right); }}return result;}
};

代码:本题还可以采用递归遍历的方式,使用前序,中序,后序都可以,这三种遍历方式都保证了先遍历左子树,再遍历右子树。注意每次更新result,只在进入到一个更大的深度,这样能保证记录的是最左边的值。

class Solution {
public://设置两个全局变量,保存最大深度和答案值,当然本题也可以将其放入函数当中,使用引用的方式int maxDepth = INT_MIN;int result;void traversal(TreeNode* cur, int depth) {if(cur->left == nullptr && cur->right == nullptr) {if (depth > maxDepth) {maxDepth = depth;result = cur->val;}}//注意必须得先遍历左边,左优先遍历,能够保证在找到最后一层的时候,赋值最左边的节点if(cur->left) traversal(cur->left, depth + 1);if(cur->right) traversal(cur->right, depth + 1);}int findBottomLeftValue(TreeNode* root) {traversal(root, 0);return result;}
};

112.路径总和

文档讲解:代码随想录路径总和

视频讲解:手撕路径总和

题目:

学习:

  1. 依据本题的意思,我们在遍历过程中,需要遍历到叶子节点才终止。注意本题不适合进行值的大小判断,因为本题的节点数值和目标值都是有可能是正,有可能是负的,因此不好设置大小判断条件。
  2. 本题可以采取前序遍历的方式,同时在遍历的过程中,不是累加各节点数值,而是通过对目标值的相减,来不断逼近目标值,这样更加的直观,且能减少不必要的变量。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:bool traversal(TreeNode* root, int count) {//减掉当前节点的值count -= root->val;//确定终止条件,遍历到叶子结点if(root->left == nullptr && root->right == nullptr && count != 0) return false;if(root->left == nullptr && root->right == nullptr && count == 0) return true;//确定单层递归逻辑,同时保证遍历过程中root不为nullptr//count非引用方式,因此为自动进行回溯if (root->left) {if(traversal(root->left, count)) return true;} if (root->right) {if(traversal(root->right, count)) return true;}return false;}bool hasPathSum(TreeNode* root, int targetSum) {if (root == nullptr) return false;return traversal(root, targetSum);}
};

113.路径总和II

题目:

学习:

  1. 本题与上题不同的在于,要找到所有的数值之和等于目标值的路径。因此我们需要遍历所有的节点,同时要持续记录数值和路径两个变量。数值通过上题,我们知道可以通过目标值不断做减法来进行记录,路径则需要我们建立一个数组来进行保存。
  2. 本题还有一个值得注意的地方,我们在递归过程中如果需要不停改变一个变量,一般采用的是引用的方式。但其实也能采用全局变量的方式,将变量写在函数外,全局变量在递归中同样会不断的被改变。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public://构造两个全局变量,存储路径和结果,取代参数引用vector<vector<int>> result;vector<int> path;//遍历树的节点,因为结果都储存在两个vector数组中,因此不需要返回值void traversal (TreeNode* root, int sum) {//确定终止条件//找到叶子节点的时候进行判断if(root->left == nullptr && root->right == nullptr && sum == 0) {result.push_back(path);return;}//如果sum!=0 直接返回if(root->left == nullptr && root->right == nullptr) return;//确定单层递归逻辑if(root->left) {path.push_back(root->left->val);traversal(root->left, sum - root->left->val);//对路径进行回溯path.pop_back();}if(root->right) {path.push_back(root->right->val);traversal(root->right, sum - root->right->val);path.pop_back();}return;}vector<vector<int>> pathSum(TreeNode* root, int targetSum) {if(root == nullptr) return result;path.push_back(root->val);traversal(root, targetSum - root->val);return result;}
};

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

文档讲解:代码随想录从中序与后序遍历系列构造二叉树

视频讲解:手撕从中序与后序遍历序列构造二叉树

题目:

学习: 本题与KMP算法一样,都是数据结构中经典的例题之一。

  1. 依据后序遍历左右中的特点我们可以知道,最后一个节点一定是根节点。而根据中序遍历左中右的特点,当我们知道谁是根节点之后,在中序遍历中根节点左边的部分就为根节点的左子树,右边的部分就为根节点的右子树。
  2. 接着我们重复上述过程,当找到根节点的左子树和右子树有哪些节点后,我们在后序遍历中也能够把除最后一个节点(根节点)以外的点,分为左子树部分和右子树部分。相对的对于这两个部分而言,由于后序遍历左右中的特点,最后一个节点就为它们各自的根节点(整棵树的中间节点)。之后再从中序遍历中依次找到根节点的左右部分即可循环下去,直到确定所有节点的位置。
  3. 如果是在纸上进行作答的话,我们根据一次次循环就很容易能够把节点加上去。但是在代码中我们要十分注意递归循环的过程,不仅要设置递归三部曲,还要划分好每次循环过程中的左子树部分和右子树部分。

本题的代码过程可以分为六步:

  1. 如果数组大小为零,说明是空节点,返回
  2. 如果不为空,取后序遍历数组的最后一个元素作为根节点元素。
  3. 找到后序遍历数组最后一个元素在中序遍历数组的位置,作为左右子树切割点。
  4. 切割中序遍历数组,切成中序左数组和中序右数组。
  5. 切割后序遍历数组,注意这里分割的方法是通过第4部分割出的两个数组来进行分割的,因为中序遍历数组中,中序左数组的个数(左子树)一定和后序遍历数组中后序左数组(左子树)的个数是一样的,右子树同理。(其实我们在用纸笔解答的时候,也是通过中序遍历数组分割后的结果,来推导后序遍历数组中的左右子树部分)
  6. 递归处理左区间和右区间。

代码:

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

代码:本题也可以通过设置下标来设置左右子树区间

 

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
private:// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {if (postorderBegin == postorderEnd) return NULL;int rootValue = postorder[postorderEnd - 1];TreeNode* root = new TreeNode(rootValue);if (postorderEnd - postorderBegin == 1) return root;int delimiterIndex;for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;}// 切割中序数组// 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)int leftInorderBegin = inorderBegin;int leftInorderEnd = delimiterIndex;// 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)int rightInorderBegin = delimiterIndex + 1;int rightInorderEnd = inorderEnd;// 切割后序数组// 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)int leftPostorderBegin =  postorderBegin;int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size// 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin);int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  postorder, leftPostorderBegin, leftPostorderEnd);root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);return root;}
public:TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {if (inorder.size() == 0 || postorder.size() == 0) return NULL;// 左闭右开的原则return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());}
};

105.从前序与中序遍历序列构造二叉树 

题目:

学习:本题和上一题一样,只不过后序换为前序遍历后,根节点的寻找变为了找前序遍历数组的第一个节点作为根节点,剩下的同样是依据需要划分不同左右子树区间,进行递归。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
private:TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {if (preorderBegin == preorderEnd) return NULL;int rootValue = preorder[preorderBegin]; // 注意用preorderBegin 不要用0TreeNode* root = new TreeNode(rootValue);if (preorderEnd - preorderBegin == 1) return root;int delimiterIndex;for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;}// 切割中序数组// 中序左区间,左闭右开[leftInorderBegin, leftInorderEnd)int leftInorderBegin = inorderBegin;int leftInorderEnd = delimiterIndex;// 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)int rightInorderBegin = delimiterIndex + 1;int rightInorderEnd = inorderEnd;// 切割前序数组// 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)int leftPreorderBegin =  preorderBegin + 1;int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin; // 终止位置是起始位置加上中序左区间的大小size// 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)int rightPreorderBegin = preorderBegin + 1 + (delimiterIndex - inorderBegin);int rightPreorderEnd = preorderEnd;root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  preorder, leftPreorderBegin, leftPreorderEnd);root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd);return root;}public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {if (inorder.size() == 0 || preorder.size() == 0) return NULL;// 参数坚持左闭右开的原则return traversal(inorder, 0, inorder.size(), preorder, 0, preorder.size());}
};

注:本题还能采用迭代的方式,等我二刷试试。

总结 

今天题目虽然不多,但是难度都很大,需要反复学习理解。

  1. 左下角的值要避免成为找最左边的左叶子节点的值。
  2. 路径总和要注意对路径中数值的处理,以及路径总和II中对每一条路径的保存和回溯,都需要注意。
  3. 从中序与后序遍历构造二叉树和从前序与中序遍历构造二叉树,理解上虽然没什么问题,但是代码书写上难度很大,还需要多加练习。

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

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

相关文章

[240621] Anthropic 发布了 Claude 3.5 Sonnet AI 助手 | Socket.IO 拒绝服务漏洞

目录 Anthropic 发布 Claude 3.5 Sonnet AI 助手Scoket.IO 拒绝服务漏洞&#xff08;CVE-2024-38355&#xff09; Anthropic 发布 Claude 3.5 Sonnet AI 助手 Claude 3.5 Sonnet: 更智能、更快速、更安全的 AI 助手 一、 引言 Anthropic 发布了 Claude 3.5 Sonnet&#xff0…

docker +tmux 远程本地gdb调试

文章目录 远程调试docker pull失败docker上容器运行程序失败宿主机远程调试docker中运行的程序环境准备调试步骤 本地调试bugpwngdbtmuxpwngdb和tmux优化~~感觉用了大佬的镜像后宿主机来做pwn题都不香了~~ 远程调试 但目前不知道如何实现可以边跟着脚本运行调试的方法 docker…

前端刻度条

前端刻度条可以通过CSS和JavaScript实现。以下是一个简单的示例&#xff1a; HTML: <div class"progress-bar"><div class"progress"></div> </div>CSS: .progress-bar {width: 200px;height: 20px;border: 1px solid #ccc;bac…

三维渲染中物体与背景间的自然过渡

在三维渲染中&#xff0c;实现物体与背景间的自然过渡是提升渲染图像真实感和美观度的关键环节。以下是几种实现这一自然过渡的主要技术和方法&#xff1a; 1. 景深效果 景深&#xff08;Depth of Field, DoF&#xff09;模拟真实相机的特性&#xff0c;使得焦点之外的物体模…

sqlalchemy使用on_duplicate_key_update在尝试插入重复键的数据时更新记录

sqlalchemy使用on_duplicate_key_update在尝试插入重复键的数据时更新记录 在SQLAlchemy中&#xff0c;在尝试插入重复键的数据时更新记录&#xff0c;可以使用on_duplicate_key_update方法。这个方法是在SQLAlchemy 1.4版本中引入的&#xff0c;它允许你定义在发生键重复时&a…

慢阻肺患者为何容易营养不良?朗格力教你轻松改善

#肺科营养#朗格力#班古营养#复合营养素#肺部营养#肺部健康# 慢阻肺是我国常见的、高患病率的慢性呼吸系统疾病,会对肺结构和功能产生影响,从而引起各种不良反应,其中营养不良是常见并发症之一。慢阻肺为什么会发生营养不良?营养不良又是怎么伤害慢阻肺的呢?为什么像班古精准…

安卓逆向案例——XX电影网

安卓app逆向——XX电影网 请求数据 GET /user/qk_login?requestyiSK6f7UI8TdTk5tmbx6sl3qCEL8Mn38S6h9c%2BeVxcs%3D HTTP/1.1 pid: 236 ver: 100/152/2016020901 Did: b70efb1db740444f osv: 33 devicemodel: Xiaomi/Mi 10 osvr: 13 key: 88b517dde01a39730e28f8de8678fb6d …

鸿蒙开发:【进程模型概述】

进程模型概述 系统的进程模型如下图所示&#xff1a; 应用中&#xff08;同一包名&#xff09;的所有PageAbility、ServiceAbility、DataAbility、FormAbility运行在同一个独立进程中&#xff0c;即图中绿色部分的“Main Process”。 WebView拥有独立的渲染进程&#xff0c;即…

什么是光子带隙光纤?

长期以来,光纤通信的发展受到纤芯材料特性的限制,特别是损耗特性。二氧化硅在可见光至近红外波长范围内损耗低,与激光器工作波长相匹配,因此成为长途电信应用中光纤纤芯的首选材料。 这类光纤的纤芯是实心的,传输原理是基于全内反射(Total Internal Reflection, TIR),其…

ServBay 下一代Web开发环境

ServBay是一个集成式、图形化的本地化Web开发环境。开发者通过ServBay几分钟就能部署一个本地化的开发环境。解决了Web开发者&#xff08;比如PHP、Nodejs&#xff09;、测试工程师、小型团队安装和维护开发测试环境的问题&#xff0c;同时可以快速的进行环境的升级以及维护。S…

Docker:认识Docker Host/Container/none网络

文章目录 Docker Host网络认识Docker Host网络实际操作网络模式区别使用场景 Docker Container网络认识Docker Container网络操作实例使用场景 Docker None网络使用场景 Docker Host网络 认识Docker Host网络 Docker容器运行默认会分配独立的Network Namespace隔离子系统&…

在公司的一些笔记

6.19 记住挂载在windows上的账户是DAHUATECH\401593&#xff0c;不是401593Windows与linux不能同时挂载在虚拟盘上 6.21 /******************************************************************************* pdc_ledSy7806e.c* * Description: 提供I2C访问sy7806e。 * * …

不破不立,B站终于跳出“舒适圈”?

哔哩哔哩已经很久没有这么振奋人心的时刻了。 6月19日&#xff0c;哔哩哔哩当日股价涨超18%&#xff0c;最高达到145.6元每股&#xff0c;时隔11个月&#xff0c;再次回归高位。从时间线上看&#xff0c;这次的股价大涨明显与哔哩哔哩刚&#xff08;以下简称“B站”&#xff0…

基于单电阻采样的电流重构

1. 单电阻采样电流重构原理 图1(a)所示是电压型三相逆变器,定义三相开 关信号为 Sa 、Sb 、Sc 。当 Sa = 1 表示A相上桥臂导 通,下桥臂关断;Sa = 0 表示相反。三相逆变器采用 SVPWM调制方式控制,有8种开关工作状态,包括 6个非零电压矢量V1 ~ V6 和2个零电压矢量V0 、V7…

使用Lua脚本保证原子性的Redis分布式锁实现

这是原来的代码&#xff1a; Override public void unlock() {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 判断标示是否一致String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name);if (threadId.equals(id)) {// 释放锁st…

约束求解器方案设计

1.约束求解介绍 给定一个几何对象&#xff08;点、直线段、圆、圆弧、平面等&#xff09;的集合G和一个关于集合G中几何对象之间约束&#xff08;点的位置、直线段的长度、圆弧对应的圆心角角度、垂直、相切等&#xff09; 的集合C&#xff0c;则在二元组(G&#xff0c;C)中根…

入门Ansible常用模块

自动化运维Devops-Ansible Ansible是新出现的自动化运维工具&#xff0c;基于Python 开发&#xff0c;集合了众多运维工具&#xff08;puppet 、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置 、批量程序部署、批量运行命令 等功能。Ansible…

Mongodb UPDATE使用$slice限制更新后数组长度

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第73篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

【如何让新增的Android.mk参与编译】

步骤1&#xff1a; 你需要在你新增的Android.mk目录以上的位置找一个已有的Android.mk 步骤2&#xff1a; 在原本已有的Android.mk中加入&#xff1a; //这是你新增的Android.mk文件的路径 include $(LOCAL_PATH)/xxx/xxx/Android.mk如果有些多可以这样写 //dir1 dir2是你新…

58-DOS与DDOS分析(正常TCP会话与SYN Flood攻击、ICMP Flood 攻击、SNMP放大攻击等)

目录 正常 TCP 会话与 SYN Flood 攻击 1、正常的三次握手过程&#xff1a; 2、 SYN Flood 攻击 一、攻击windows系统&#xff1a; 二、攻击web网站 &#xff1a; 拒绝服务攻击工具-Hping3-Syn Flood 攻击 拒绝服务攻击工具--Hping3--ICMP Flood 攻击 sockstress攻击 So…