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 不要用0
        TreeNode* 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. 从中序与后序遍历构造二叉树和从前序与中序遍历构造二叉树,理解上虽然没什么问题,但是代码书写上难度很大,还需要多加练习。

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

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

相关文章

Elk安装及使用

es安装及使用 单机版安装 集群安装 132 node-01 133 node-02 135 node-03 日志用户权限有问题 看日志 解决方案&#xff1a; 出现错误后&#xff0c;再次重启前&#xff0c;需要删除三个节点/data/下的内容 9300-http 9300-tcp logstasha安装及使用 Ssh错误 Yum安装默认路…

职场记 | 有些人的成功真的不是偶然

今天跟大家聊一聊雷总的成长记&#xff0c;希望给职场中的朋友们一点启发&#xff1a; 强烈的创业精神与持续的创新意识 雷军自大学时期起就展现出了强烈的创业热情。他不仅在求学期间积极参与创业活动&#xff0c;更在毕业后迅速踏上创业道路&#xff0c;创立了多家知名企业…

大模型时代,新手和程序员如何转型入局AI行业?

在近期的全国两会上&#xff0c;“人工智能”再次被提及&#xff0c;并成为国家战略的焦点。这一举措预示着在接下来的十年到十五年里&#xff0c;人工智能将获得巨大的发展红利。技术革命正在从“互联网”向“人工智能”逐步迈进&#xff0c;我将迎来新一轮技术革新和人才需求…

NetSuite 不同类型Item的公司间交易科目的设置

我们知道&#xff0c;NetSuite中有Intercompany Preferences的设置&#xff0c;如下所示&#xff0c;分别涉及到公司间应收、公司间应付、公司间收入、公司间费用以及公司间成本共5个科目&#xff0c;非常明确清晰。 最近用户遇到的场景是&#xff0c;如果是Non-Inventory Item…

史上最全的整合Harbor安装教程,哈哈哈哈

一、安装docker 下载地址&#xff1a;https://download.docker.com/linux/static/stable/x86_64/docker-23.0.4.tgz 1.1 解压二进制包 wget https://download.docker.com/linux/static/stable/x86_64/docker-23.0.4.tgz tar zxvf docker-23.0.4.tgz mv docker/* /usr/bin1.2…

【C语言】16.动态内存管理

文章目录 1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free 3.calloc和realloc3.1 calloc3.2 realloc 4.常见的动态内存的错误4.1 对NULL指针的解引⽤操作4.2 对动态开辟空间的越界访问4.3 对⾮动态开辟内存使⽤free释放4.4 使⽤free释放⼀块动态开辟内存的⼀部分4.5…

对红酒数据集,分别采用决策树算法和随机森林算法进行分类。

1.导入所需要的包 from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split 2.导入数据&#xff0c;并且对随机森林和决策数进…

每月 GitHub 探索|10 款引领科技趋势的开源项目

1.IT-Tools 仓库名称&#xff1a; CorentinTh/it-tools 截止发稿星数: 16842 (近一个月新增:5744) 仓库语言: Vue 仓库开源协议&#xff1a; GNU General Public License v3.0 引言 CorentinTh/it-tools 是一个开源项目&#xff0c;提供各种对开发者友好的在线工具&#xff0…

教师数字素养标准

老师们有没有想过如何让自己的课堂更加生动、互动&#xff0c;甚至超越传统的教学模式&#xff1f;是否思考过&#xff0c;数字技术如何成为我们教学的得力助手&#xff0c;让我们的课堂焕发出新的活力&#xff1f; 数字素养&#xff0c;这个听起来充满科技感的词汇&#xff0c…

接口实现多态

多态&#xff1a; 父类的引用类型变量指向了子类的对象或者是接口类型的引用类型变量指向了接口实现类 的对象。 实现关系下的多态&#xff1a; 接口 变量 new 接口实现类的对象。 接口 public abstract interface InterA {public abstract void show();}实现类 public c…

文件上传漏洞-上篇

一、概述 文件上传漏洞可以说是日常渗透测试中用得最多的一个漏洞&#xff0c;用它获得服务器权限最快最直接。在web程序中&#xff0c;经常需要用到文件上传的功能。如用户或者管理员上传图片&#xff0c;或者其它文件。如果没有限制上传类型或者限制不严格被绕过&#xff0c…

数据结构6---树

一、定义 树(Tree)是n(n>0)个结点的有限集。当n0时成为空树,在任意一棵非空树中: 1、有且仅有一个特定的称为根(Root)的结点; 2、当n>1时,其余结点可分为m(m>日)个互不相交的有限集T1、T2、...、 Tm&#xff0c;其中每一个集合本身又是一棵树&#xff0c;并且称为根的…

LabVIEW项目中的常见电机及其特点分析

在LabVIEW项目中&#xff0c;电机的选择对系统的性能和应用效果至关重要。常见电机类型包括直流电机&#xff08;DC Motor&#xff09;、步进电机&#xff08;Stepper Motor&#xff09;、交流感应电机&#xff08;AC Induction Motor&#xff09;和无刷直流电机&#xff08;BL…

【MySQL进阶之路 | 高级篇】InnoDB搜索引擎行格式

1. COMPACT行格式 COMPACT行格式是MySQL5.1的默认行格式.其结构示意图如下. 大体可以分为两部分. 记录的额外信息.这里面有包括变长字段长度列表&#xff0c;NULL值列表和记录头信息.记录的真实数据. (1).变长字段长度列表 MySQL支持一些变长的数据类型.比如VARCHAR(m), VA…

【扫雷游戏】C语言实现

机器学习&#xff1a;Transformer框架理论详解和代码实现>Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属…

别让破损安全鞋,成为你工作中无法预见的“隐形杀手”

在日常生活中&#xff0c;安全鞋是我们的“守护神”&#xff0c;默默守护着我们的双脚&#xff0c;抵御着来自工作环境的各种风险。然而&#xff0c;当安全鞋出现破损时&#xff0c;我们却常常视而不见&#xff0c;以为这只是微不足道的小事&#xff0c;很多人可能会选择“将就…

ASP.NET Core 6.0 使用 Log4Net 和 Nlog日志中间件

前言 两年前,浅浅的学过 .NET 6,为啥要记录下来,大概是为了以后搭架子留下引线,还有抛砖引玉。 1. 环境准备 下载 建议使用 Visual Studio 2022 开发版 官网的下载地址:Visual Studio 2022 IDE - 适用于软件开发人员的编程工具借助 Visual Studio 设计,具有自动完成…

SFF2004A-ASEMI无人机专用SFF2004A

编辑&#xff1a;ll SFF2004A-ASEMI无人机专用SFF2004A 型号&#xff1a;SFF1006A 品牌&#xff1a;ASEMI 封装&#xff1a;ITO-220AC 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;20A 最大循环峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;400V 最…

智能体合集

海外版coze: 前端代码助手 后端代码助手&#xff1a; 前端代码助手&#xff1a;

大数据-数据分析师利用excel绘图

你会用excel&#xff0c;统计数据吗&#xff1f;我是大数据工程师&#xff0c;但是我不会excel。那咋办&#xff1f; 用sql&#xff0c;统计&#xff0c;导出到excel&#xff0c;在用excel统计。本文主要讨论的是导出到excel后&#xff0c;画图。 图是什么&#xff1f; x和y…