24暑假算法刷题 | Day16 | LeetCode 513. 找树左下角的值,112. 路径总合,106. 从中序和后序遍历序列构造二叉树

目录

  • 513. 找树左下角的值
    • 题目描述
    • 题解
  • 112. 路径总合
    • 题目描述
    • 题解
  • 106. 从中序和后序遍历序列构造二叉树
    • 题目描述
    • 题解


513. 找树左下角的值

点此跳转题目链接

题目描述

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

在这里插入图片描述

输入: root = [2,1,3]
输出: 1

示例 2:

在这里插入图片描述

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1

题解

题目说的很清楚了,目标先是 “最底层” ,再是 “最左边” ,所以考虑先用层序遍历得到最底层的节点值数组,然后返回数组第一个值,即为“左下角”的值了:

vector<vector<int>> levelOrder(TreeNode *root)
{vector<vector<int>> res;queue<TreeNode *> q;if (!root)return res;q.push(root);while (!q.empty()){int size = q.size(); // 注意!先记录当前队长,因为之后会变vector<int> level;   // 当前这一层的节点值for (int i = 0; i < size; ++i){level.push_back(q.front()->val);if (q.front()->left)q.push(q.front()->left);if (q.front()->right)q.push(q.front()->right);q.pop();}res.push_back(level);}return res;
}int findBottomLeftValue(TreeNode *root)
{vector<vector<int>> levels = levelOrder(root); // 偷个懒,直接调用之前写过的层序遍历函数return levels[levels.size() - 1][0];
}

此外,我们还是可以考虑一下递归法,逐步递归到左下角。递归出口的判断也比较简单,即:若当前节点的左孩子是叶子节点,则根据其深度判断是否要更新左下角值:

int maxDepth = -1;
int leftBottonVal = 0;void traversal(TreeNode *root, int depth)
{// 递归出口:叶子节点if (!root->left && !root->right && depth > maxDepth){leftBottonVal = root->val; // 更新当前探索到的左下角值maxDepth = depth;          // 更新当前探索到的最大深度return;}// 先处理左孩子,这样保证同一层记录最左节点if (root->left)traversal(root->left, depth + 1);// 再处理右孩子if (root->right)traversal(root->right, depth + 1);
}int findBottomLeftValue_II(TreeNode *root)
{traversal(root, 0);return leftBottonVal;
}

112. 路径总合

点此跳转题目链接

题目描述

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

在这里插入图片描述

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

  • 树中节点的数目在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

题解

首先可以用DFS递归秒杀:

bool hasPathSum(TreeNode *root, int targetSum)
{// 递归出口1:空节点if (!root)return false;// 递归出口2:加上当前节点值求和等于targetSum,且当前节点为叶子节点if (root->val == targetSum && !root->left && !root->right)return true;// 递归探索左右子树return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}

然后再想想迭代法,发现这题和 257. 二叉树的所有路径 其实如出一辙(具体思路和注意细节见那篇题解),只需要将其中记录路径的逻辑替换成计算路径节点值之和就行了:

bool hasPathSum_II(TreeNode *root, int targetSum) {// 基于前序遍历的统一迭代法实现if (!root)return false;stack<TreeNode*> nodeSt;nodeSt.push(root);stack<int> sumSt;sumSt.push(root->val);while (!nodeSt.empty()) {TreeNode *node = nodeSt.top();nodeSt.pop();int sum = sumSt.top(); sumSt.pop();if (node) {if (node->right) {nodeSt.push(node->right); // 右sumSt.push(sum + node->right->val);}if (node->left) {nodeSt.push(node->left); // 左sumSt.push(sum + node->left->val);}nodeSt.push(node); // 中nodeSt.push(nullptr); // 空指针标记sumSt.push(sum);}else {if (sum == targetSum && !nodeSt.top()->left && !nodeSt.top()->right)return true;nodeSt.pop();}}return false;
}

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

点此跳转题目链接

题目描述

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例 1:

在这里插入图片描述

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorderpostorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

题解

蛮锻炼思维的一道题目 🚀

我觉得核心是要理解两点:

1️⃣ 中序序列 inorder 的第 i 个值对应的节点,其左、右子树中的节点对应的就是 i 左边、右边的序列

2️⃣ 后序序列 postorder 的最后一个值,就是该序列对应的二叉树的根节点

这两点不难由中序遍历和后序遍历的定义和性质得出。于是,我们可以据此从根节点递归地“生长”出二叉树:

  • postorder 的最后一个值 r 为根节点 root 的值

  • inorder 中找到 r 对应的下标 i ,则 i 左边的值就对应着 root 的左子树、 i 右边的值就对应着 root 的右子树

    题目说了:inorderpostorder 都由 不同 的值组成,所以可以根据数值(唯一)对应去找下标

  • i 为“分割点”,将 inorder 拆分为左右两部分;相应地也将 postorder 拆分,满足

    • 排除 postorder 的最后一个值(因为它是分割点,对应着当前根节点)
    • postorder 的左右部分长度和 inorder 的左右部分长度相同
  • 按照上述方法递归地生成 root 的左右子树,递归出口为序列切片已经不可分割则返回空指针

此思路更详细的分析可参阅 代码随想录此题讲解 。我的代码如下,包括了一个调试函数 log ,用于debug的时候检查每次生成的中序、后序左右子序列长度是否一致、数值是否一一对应(即它们都表示着同一棵子树):

class Solution
{
private:// 由于所有值各不相同,先用哈希表存储其在中序、后序数组中的下标unordered_map<int, int> inMap, postMap;void getIndexMap(const vector<int> &inorder, const vector<int> &postorder){for (int i = 0; i < inorder.size(); ++i){inMap[inorder[i]] = i;postMap[postorder[i]] = i;}}// todo debugvoid log(const vector<int> &inorder, const vector<int> &postorder,int inLeftB, int inLeftE, int inRightB, int inRightE,int postLeftB, int postLeftE, int postRightB, int postRightE){cout << "----------inorder left----------" << endl;for (int i = inLeftB; i < inLeftE; ++i)cout << inorder[i] << " ";cout << endl;cout << "---------postorder left---------" << endl;for (int i = postLeftB; i < postLeftE; ++i)cout << postorder[i] << " ";cout << endl;cout << "----------inorder right---------" << endl;for (int i = inRightB; i < inRightE; ++i)cout << inorder[i] << " ";cout << endl;cout << "-----------post right-----------" << endl;for (int i = postRightB; i < postRightE; ++i)cout << postorder[i] << " ";cout << endl;cout << "********************************" << endl;}public:/// @brief 根据中序、后序数组的切片(左闭右开),递归获取树中的节点/// @param postorder 后序遍历数组(用于初始化当前的新节点)/// @param inBegin 当前中序数组的起始指针/// @param inEnd 当前中序数组的结尾指针/// @param postBegin 当前后序数组的起始指针/// @param postEnd 当前后序数组的结尾指针/// @return 当前获得的节点TreeNode *getNode(const vector<int> &inorder, const vector<int> &postorder,int inBegin, int inEnd, int postBegin, int postEnd){if (inBegin == inEnd)return nullptr;// 后序数组的最后一个值,就是当前子树根节点的值TreeNode *root = new TreeNode(postorder[postEnd - 1]);// 定位该节点在中序数组中的位置,作为分割点int cutPoint = inMap[root->val];// 将中序数组拆分,则root的左右子树对应节点值也就是拆分后的左右部分int leftInBegin = inBegin, leftInEnd = cutPoint;     // 左半部分int rightInBegin = cutPoint + 1, rightInEnd = inEnd; // 右半部分// 相应的,将后序数组也按照同样位置拆分int leftPostBegin = postBegin, leftPostEnd = postBegin + (cutPoint - inBegin);     // 左半部分int rightPostBegin = postBegin + (cutPoint - inBegin), rightPostEnd = postEnd - 1; // 右半部分// log(//     inorder, postorder,//     leftInBegin, leftInEnd, rightInBegin, rightInEnd,//     leftPostBegin, leftPostEnd, rightPostBegin, rightPostEnd// );// 获取当前root的左右子树,然后返回当前rootroot->left = getNode(inorder, postorder, leftInBegin, leftInEnd, leftPostBegin, leftPostEnd);root->right = getNode(inorder, postorder, rightInBegin, rightInEnd, rightPostBegin, rightPostEnd);return root;}// 递归、多指针解决TreeNode *buildTree(vector<int> &inorder, vector<int> &postorder){getIndexMap(inorder, postorder);return getNode(inorder, postorder, 0, inorder.size(), 0, postorder.size());}
};

上面的写法比较便于理解算法和调试,将 log 那部分代码注释取消,运行题目描述中的示例1,可以看到每次递归生成的子序列。以第一次分割为例,有输出:

----------inorder left----------
9
---------postorder left---------
9
----------inorder right---------
15 20 7
-----------post right-----------
15 7 20
********************************
...

可以看到,中序、后序的左、右子序列都是一一对应的。

实际上,后序遍历的左右子序列没必要每次都全部维护,可以发现代码中我们实际用到的也就是其尾指针(每次指向当前的根节点)。所以可以简化代码如下(来源:LeetCode官方题解):

class Solution {int post_idx;unordered_map<int, int> idx_map;
public:TreeNode* helper(int in_left, int in_right, vector<int>& inorder, vector<int>& postorder){// 如果这里没有节点构造二叉树了,就结束if (in_left > in_right) {return nullptr;}// 选择 post_idx 位置的元素作为当前子树根节点int root_val = postorder[post_idx];TreeNode* root = new TreeNode(root_val);// 根据 root 所在位置分成左右两棵子树int index = idx_map[root_val];// 下标减一post_idx--;// 构造右子树root->right = helper(index + 1, in_right, inorder, postorder);// 构造左子树root->left = helper(in_left, index - 1, inorder, postorder);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {// 从后序遍历的最后一个元素开始post_idx = (int)postorder.size() - 1;// 建立(元素,下标)键值对的哈希表int idx = 0;for (auto& val : inorder) {idx_map[val] = idx++;}return helper(0, (int)inorder.size() - 1, inorder, postorder);}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/solutions/426738/cong-zhong-xu-yu-hou-xu-bian-li-xu-lie-gou-zao-14/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

相关文章

虚继承(C++)

目录 菱形继承 虚继承 虚继承原理 虚继承使用注意事项&#xff1a; 不要把所有的遗憾都留给未来&#xff0c;趁年轻出去走走&#xff0c; 让我们用心去感受这个世界&#xff0c;用脚步去丈量这个世界的距离。 这里是来自M--Y的专栏&#xff1a;C启&#xff08;&#xff09;航…

SAP ABAP 批导函数(封装版)

EXCEL批导函数相信大家熟悉的不能再熟悉了&#xff0c;但是特殊场景不同函数对工作的影响真的很大。 整理本篇文章是因为公司电脑统一使用了加密系统&#xff0c;通过先前的统一函数无法直接上传&#xff0c;每次都要先另存为TEXT文本后上传&#xff0c;用户体验非常差&#x…

持续集成01--Git版本管理及基础应用实践

前言 本系列文章旨在深入探讨持续集成/持续部署&#xff08;Continuous Integration/Continuous Deployment, CI/CD&#xff09;流程中的各个环节&#xff0c;而本篇将聚焦于Git版本管理及其基本应用。通过本文&#xff0c;读者将了解到Git的基本原理、安装配置、基本命令以及如…

GuLi商城-商品服务-API-品牌管理-品牌分类关联与级联更新

先配置mybatis分页&#xff1a; 品牌管理增加模糊查询&#xff1a; 品牌管理关联分类&#xff1a; 一个品牌可以有多个分类 一个分类也可以有多个品牌 多对多的关系&#xff0c;用中间表 涉及的类&#xff1a; 方法都比较简单&#xff0c;就不贴代码了

无线物联网新时代,RFID拣货标签跟随潮流

拣选技术的演变历程&#xff0c;本质上是从人力操作向自动化、智能化转型的持续进程。近期&#xff0c;“货寻人”技术成为众多企业热烈追捧的对象&#xff0c;它可以根据企业的特定需求&#xff0c;从众多拣选方案中选出最优解。那么&#xff0c;在采用“货到人”拣选技术时&a…

【python】OpenCV—Scanner

文章目录 1、需求描述2、代码实现3、涉及到的库函数cv2.arcLengthcv2.approxPolyDPskimage.filters.threshold_localimutils.grab_contours 4、完整代码5、参考 1、需求描述 输入图片 扫描得到如下的结果 用OpenCV构建文档扫描仪只需三个简单步骤: 1.边缘检测 2.使用图像中…

VS code配置docker远程连接

一 前置条件 1、本地已安装docker 2、服务端docker已配置Docker配置远程连接 二 VScode安装docker扩展 三 执行docker命令 1、切换到远程docker节点 docker context create remote-docker --docker "hosthttp://192.168.6.9:2375" 2、使用远程节点 docker cont…

PyTorch 深度学习实践-逻辑斯蒂回归

视频指路 参考博客笔记 参考笔记二 用来分类的模型 说明&#xff1a;1、 逻辑斯蒂回归和线性模型的明显区别是在线性模型的后面&#xff0c;添加了激活函数(非线性变换) ​ 2、分布的差异&#xff1a;KL散度&#xff0c;cross-entropy交叉熵 现在损失函数衡量不是距离而是分布…

学习react-环境手脚架页面路由

1. 搭建环境 安装node和npm 在下面网址下载node&#xff0c;并安装 https://nodejs.cn/ #检测是否ok node -v npm -v安装react npm install -g create-react-app2. 创建手脚架&#xff08;TypeScript&#xff09; create-react-app my-app --template typescript cd my-a…

昇思25天学习打卡营第15天|两个分类实验

打卡 目录 打卡 实验1&#xff1a;K近邻算法实现红酒聚类 数据准备 模型构建--计算距离 计算演示 模型预测 实验2&#xff1a;基于MobileNetv2的垃圾分类 任务说明 数据集 参数配置&#xff08;训练/验证/推理&#xff09; 数据预处理 MobileNetV2模型搭建 Mobile…

AGI 之 【Hugging Face】 的【零样本和少样本学习】之三 [无标注数据] 的简单整理

AGI 之 【Hugging Face】 的【零样本和少样本学习】之三 [无标注数据] 的简单整理 目录 AGI 之 【Hugging Face】 的【零样本和少样本学习】之三 [无标注数据] 的简单整理 一、简单介绍 二、零样本学习 (Zero-shot Learning) 和少样本学习 (Few-shot Learning) 1、零样本学…

钡铼分布式 IO 系统 OPC UA边缘计算耦合器BL205

深圳钡铼技术推出的BL205耦合器支持OPC UA Server功能&#xff0c;以服务器形式对外提供数据。符合IEC 62541工业自动化统一架构通讯标准&#xff0c;数据可以选择加密&#xff08;X.509证书&#xff09;、身份验证方式传送。 安全策略支持basic128rsa15、basic256、basic256s…

【Git远程操作】理解分布式管理 | 创建远程仓库

目录 1.理解分布式管理 多人协作开发 2.创建远程仓库 2.1仓库名&路径 2.2初始化仓库&设置模板 1.理解分布式管理 目前我们学习的所有内容都是在本地来完成的。&#xff08;add /commit /版本撤销回退/分支管理&#xff09; Git是一个分布式 的版本控制系统。 分支…

数据挖掘与分析部分实验与实训项目报告

一、机器学习算法的应用 1. 朴素贝叶斯分类器 相关代码 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.naive_bayes import GaussianNB, MultinomialNB from sklearn.metrics import accuracy_score # 将数据加载到DataFrame中&a…

算法工程师第十四天(找树左下角的值 路径总和 从中序与后序遍历序列构造二叉树 )

参考文献 代码随想录 一、找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7 层次遍历&#…

【hadoop大数据集群 2】

【hadoop大数据集群 2】 文章目录 【hadoop大数据集群 2】1. 虚拟机克隆2. 时间同步3. 环境变量配置、启动集群、关闭集群 1. 虚拟机克隆 克隆之后一定要重新生成新虚拟机唯一的MAC地址和UUID等&#xff0c;确保新虚拟机与源虚拟机在网络拓扑中不发生冲突。 注意1.生成新的MA…

Pytorch学习笔记day3——用神经网络学习一组函数

好的&#xff0c;我们开始吧。首先第一个问题&#xff0c;神经网络的本质是什么&#xff1f;是古典主义的人类的神经元吗&#xff1f;绝对不是&#xff0c;他只是一个优化函数 y f θ ( x ) y f_{\theta}(x) yfθ​(x) 这和小学学到的线性函数拟合并无本质区别。只是其中参数…

uniapp form表单校验

公司的一个老项目&#xff0c;又要重新上架&#xff0c;uniapp一套代码&#xff0c;打包生成iOS端发布到App Store&#xff0c;安卓端发布到腾讯应用宝、OPPO、小米、华为、vivo&#xff0c;安卓各大应用市场上架要求不一样&#xff0c;可真麻烦啊 光一个表单校验&#xff0c;…

云手机结合自主ADB命令接口 提升海外营销效率

现在&#xff0c;跨境电商直播已经成为在线零售的重要渠道&#xff0c;在大环境下&#xff0c;确保直播应用的稳定性和用户体验至关重要。 云手机支持自主ADB命令接口&#xff0c;为电商直播营销提供了技术支持&#xff0c;使得应用开发、测试、优化和运维更加高效。 什么是A…

【sklearn | 7】:scikit-learn项目实战指南

引言 在数据科学和机器学习领域&#xff0c;Python以其简洁的语法和强大的库支持&#xff0c;成为了许多开发者和研究者的首选语言。而在众多Python机器学习库中&#xff0c;scikit-learn以其易用性、灵活性和强大的算法集合&#xff0c;成为了最受欢迎的库之一。本文将深入探…