Studying-代码随想录训练营day19| 530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236.二叉树的最近公共祖先

news2024/11/17 9:30:17

第十九天,二叉树part06,二叉树的道路任重而道远💪

目录

530.二叉搜索树的最小绝对差

501.二叉搜索树中的众数 

236.二叉树的最近公共祖先 

总结 


530.二叉搜索树的最小绝对差

文档讲解:代码随想录二叉搜索树的最小绝对差

视频讲解:手撕二叉搜索树的最小绝对差

题目:

学习:

如果本题是普通二叉树,可以采取任意一种遍历方式,把二叉树的各节点数值保存在数组中,然后对数组进行排序,最后找到两不同节点之间的最小差值即可。

但本题是二叉搜索树,依据二叉搜索树的特点,对二叉搜索树进行中序遍历,得到的会是一个单调递增的序列,这样无需排列,就能够找到最小差值。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root) {
    if (root == NULL) return;
    traversal(root->left);
    vec.push_back(root->val); // 将二叉搜索树转换为有序数组
    traversal(root->right);
}
public:
    int getMinimumDifference(TreeNode* root) {
        vec.clear();
        traversal(root);
        if (vec.size() < 2) return 0;
        int result = INT_MAX;
        for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值
            result = min(result, vec[i] - vec[i-1]);
        }
        return result;
    }
};

上述方法需要把二叉树所有节点保存在数组中,但事实上我们在进行中序遍历的过程中,就可以进行比较了,方法和98.验证二叉搜索树中使用的相同,可以使用双指针法,一个指针是当前遍历的节点,一个指针是当前节点的前驱节点。再设置一个int型result变量,用来接收最小值,之后除第一个节点外,每次遍历的时候都进行相减并比较,更新最小值。

代码:中序遍历(递归法)

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    int minnum = INT_MAX;
    TreeNode* pre = nullptr; //表示前一个节点
    //二叉搜索树,采用中序遍历的方式,得到的是一个递增序列
    void traversal(TreeNode* root) {
        if(root == nullptr) return;

        traversal(root->left);
        if(pre != nullptr) {
            minnum = min(root->val - pre->val, minnum);
        }
        pre = root;
        traversal(root->right);
    }
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return minnum;
    }
};

代码:中序遍历(迭代法)

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    //迭代法,采用中序遍历
    int getMinimumDifference(TreeNode* root) {
        //创建一个栈存储节点
        stack<TreeNode*> st;
        //双指针,分别指向中序遍历过程中的前后节点
        TreeNode* cur = root;
        TreeNode* pre = nullptr;
        int result = INT_MAX;
        while(cur != nullptr || !st.empty()) {
            if(cur != nullptr) {
                st.push(cur);
                cur = cur->left;  //左
            }
            else {
                cur = st.top();
                st.pop();
                if(pre != nullptr) {    //中
                    result = min(cur->val - pre->val, result);
                }
                pre = cur;  
                cur = cur->right;   //右
            }
        }
        return result;
    }
};

总结:在遇到二叉搜索树时,要时刻牢记二叉搜索树的特点。无论是求最值,求差值还是作前后节点比较,都要思考一下二叉搜索树是有序的,要利用好这一特点。


501.二叉搜索树中的众数 

文档讲解:代码随想录二叉搜索树中的众数

视频讲解:手撕二叉搜索树中的众数

学习:

同样如果本题不是二叉搜索树,只是一个普通树的话。本题可以采取的方式是,通过任意一种遍历方法,把二叉树中各节点的值保存下来。要注意这里需要统计每个数出现的频率,因此可以采用map的数据结构,且为了存储方便,可以采用哈希表的方式,让每次重复插入的时候,查找效率为O(1),key设置为节点值,value设置为频率。统计完后,再将他们放入数组中,并进行排序即可。(本题使用到了一个新的排序方法,是sort()算法内复用的)

代码:

class Solution {
private:

void searchBST(TreeNode* cur, unordered_map<int, int>& map) { // 前序遍历
    if (cur == NULL) return ;
    map[cur->val]++; // 统计元素频率
    searchBST(cur->left, map);
    searchBST(cur->right, map);
    return ;
}
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {
    return a.second > b.second;
}
public:
    vector<int> findMode(TreeNode* root) {
        unordered_map<int, int> map; // key:元素,value:出现频率
        vector<int> result;
        if (root == NULL) return result;
        searchBST(root, map);
        vector<pair<int, int>> vec(map.begin(), map.end());
        sort(vec.begin(), vec.end(), cmp); // 给频率排个序
        result.push_back(vec[0].first);
        for (int i = 1; i < vec.size(); i++) {
            // 取最高的放到result数组中
            if (vec[i].second == vec[0].second) result.push_back(vec[i].first);
            else break;
        }
        return result;
    }
};

但本题是线索二叉树, 节点之间是有序的,并且力扣上本题也希望我们使用除递归以外的额外空间开销(空间复杂度为O(1)),因此本题需要从线索二叉树的特点上入手。

  1. 由于是线索二叉树,并且本题中的二叉树内各节点的值是能够重复的,因此对其进行中序遍历,得到的是一个非递减序列,同时相同值的节点会依次排序。
  2. 我们需要统计的是值相同的点的个数,并且不使用大于O(1)的空间复杂度,因此我们需要在遍历的过程中,就把值给统计出来,并且把频率大的数加入到返回数组中(注意返回值不参与空间复杂度)。
  3. 我们可以使用两个int型变量count和maxconut,一个用来统计当前数的频率,一个用来统计最大频率。每次遍历过程中比较count和maxcount。如果count小于maxcount则继续遍历。如果count和maxcount相等,则将当前的数加入到答案数组中。如果count大于maxcount,则说明此时需要更新最大频率了,要注意的是此时不仅要更改maxcount的值,还需要将返回数组清零,因为此时数组内的数不再是最大频率的数了,而我们只需要最大频率的数,因此需要将返回数组清零,并重新将当前数加入到返回数组中。

代码:

//时间复杂度O(n)
//空间复杂度O(1)(除去递归带来的隐式空间复杂度)
class Solution {
public:
    vector<int> result;
    int maxcount = 0;  //存储最大频率
    int count = 0;     //当前频率
    TreeNode* pre = nullptr; //指向当前节点的前一个节点
    //递归法,中序遍历
    //确定返回值和参数列表,本题需要遍历所有节点,因此参数为root,本题采用全局遍历vector存储结果,因此不需要返回值。
    void traversal(TreeNode* cur) {
        if(cur == nullptr) return;

        traversal(cur->left);  //左
                               //中
        if(pre == nullptr) {   //第一个节点
            count = 1;
        }
        else if (pre->val == cur->val) { //与上一个节点数值相同
            count++;
        }
        else { //与上一个节点数值不同
            count = 1;
        }
        pre = cur; //更新上一个节点

        if (count == maxcount) {  //如果和最大统计频率一样
            result.push_back(cur->val);
        }
        if (count > maxcount) { //更新最大频率,每次出现更大频率的数时,要将答案数组清理
            maxcount = count;
            result.clear();
            result.push_back(cur->val);
        }

        traversal(cur->right);
        return;
    }
    vector<int> findMode(TreeNode* root) {
        traversal(root);
        return result;
    }
};

236.二叉树的最近公共祖先 

文档讲解:代码随想录二叉树的公共祖先

视频讲解:手撕二叉树的最近公共祖先

题目:

学习:依据本题题干和提示,需要找到两个节点的最近公共祖先,且两个节点必定存在于二叉树当中。显然本题需要遍历二叉树的所有节点,那么采取什么遍历方式比较好呢,由于本题要找到的是节点p、q的最近公共祖先,因此应该自底向上进行查找,当找到节点p或q后就把当前节点的信息返回给父节点,然后再逐级返回,这种遍历方式显然应该采用后序遍历。

本题递归三部曲的设置十分重要,我们可以通过下图来总结进行递归三部曲的设置:

1.确定返回值和参数:本题需要我们返回最近公共祖先的节点,因此本题返回值类型因改为TreeNode*,参数则因为需要遍历二叉树,因此传入root。

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)

2.确定终止条件:当节点为nullptr或者节点等于p或q时,我们将节点进行返回。

//确定终止条件
if(root == NULL) return NULL;
if(root->val == p->val || root->val == q->val) return root;

3.确定单层递归逻辑:当当前节点不是目标节点也不是空节点时,说明我们需要向下遍历了,且我们还需要设置两个TreeNode* 变量来接受左右子树遍历的结果。

//确定单层递归逻辑
//采取后续遍历的方式
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);

4.最后我们根据返回结果的四种情况进行判断和上层返回:

//四种情况
if(left != NULL && right != NULL) return root; //左右子树都有目标值(由于目标值是不同的,且二叉树内没有相同的值,因此肯定是p、q都找到了)
else if (left != NULL && right == NULL) return left; //有可能公共祖先已经找到了,并且由于右子树没有目标节点,说明当前子树不可能是公共祖先,因此返回上一个结果。
else if (left == NULL && right != NULL) return right;
else return NULL;

注意:本题是包含了p节点是q节点祖先,或者q节点是p节点祖先的情况的,因为如果p节点是q节点的祖先,那我们在遍历过程中,遍历到p节点就返回了,不会继续向下遍历,虽然这样不能遍历到q节点但是我们会把p节点一直往上返回最后输出。又由于本题中的条件p、q节点一定存在,因此我们就可以断定返回上来的p节点一定是q节点的祖先。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //确定终止条件
        if(root == NULL) return NULL;
        if(root->val == p->val || root->val == q->val) return root;

        //确定单层递归逻辑
        //采取后续遍历的方式
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);

        //四种情况
        if(left != NULL && right != NULL) return root; //左右子树都有目标值(由于目标值是不同的,且二叉树内没有相同的值,因此肯定是p、q都找到了)
        else if (left != NULL && right == NULL) return left; //有可能公共祖先已经找到了,并且由于右子树没有目标节点,说明当前子树不可能是公共祖先,因此返回上一个结果。
        else if (left == NULL && right != NULL) return right;
        else return NULL;
    }
};

总结:

  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历,通过回溯实现从底向上的遍历方式。
  2. 在回溯过程中,还需要不断比较底层递归上来的结果,也就是left和right并进行逻辑判断。
  3. 要理解为什么left为空,right不为空(或者相反),能够返回right,这本质是因为我们要返回的是查找的结果,只有在找到左右子树都不为空时,此时的第一个节点才是最近公共祖先。否则的话就可能会出现p是q的父节点或者q是p的父节点,或者已经找到了最近公共祖先节点,此时我们就需要不断的把结果进行返回。

总结 

今天的题主要练习对二叉搜索树特点的利用,以及对后序遍历和回溯过程的理解。

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

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

相关文章

2024年高性价比蓝牙耳机怎么买?揭秘超高性价比蓝牙耳机推荐

当一打开购物软件想购买一款性价比高的蓝牙耳机&#xff0c;你就会发现&#xff0c;不同的蓝牙耳机的品牌价格差距蛮大的&#xff01;几十块的随处可见&#xff0c;上千块的也琳琅满目&#xff0c;品牌和款式更是繁多&#xff0c;让人看得眼花缭乱&#xff0c;无从下手......其…

浅谈逻辑控制器之ForEach控制器

浅谈逻辑控制器之ForEach控制器 ForEach控制器是一个非常实用的功能&#xff0c;它允许用户遍历某个变量的所有值&#xff0c;并为每个值执行控制器内的子采样器或逻辑。这对于处理从先前请求&#xff08;如CSV Data Set Config、JSON Extractor、Regular Expression Extracto…

银幕光影交织,红酒香醇流淌,一场电影与红酒的绝美浪漫邂逅

在光影交错的世界里&#xff0c;红酒与电影总能在不经意间碰撞出浪漫的火花。当银幕上的角色轻启瓶盖&#xff0c;那迷人的酒香便如诗如画般弥漫开来&#xff0c;与影片的情节交织在一起&#xff0c;构成了一幅幅动人的画面。今天&#xff0c;就让我们一起走进这个充满酒香的银…

9.XSS之过滤

XSS之过滤 通过输入代码发现被过滤掉了 <script>";"666查看页面元素代码&#xff0c;被后台代码过滤了 尝试一下大小写混合使用&#xff0c;攻击代码如下&#xff1a; <sCRipT>alert(你打篮球像oldqu)</sCrIPt>如下图所示&#xff0c;大小写混…

1986-2017年 全国农村固定观察点数据

全国农村固定观察点调查体系是中国一个重要的农村社会经济调查项目&#xff0c;它通过长期跟踪调查固定不变的村庄和农户&#xff0c;收集连续性数据&#xff0c;以获取农村基层的动态信息。这些数据不仅全面反映了农户及其家庭成员在生产、消费、就业、生活等方面的情况&#…

05-java基础——循环习题

循环的选择&#xff1a;知道循环的次数或者知道循环的范围就使用for循环&#xff0c;其次再使用while循环 猜数字 程序自动生成一个1-100之间的随机数&#xff0c;在代码中使用键盘录入去猜出这个数字是多少&#xff1f; 要求&#xff1a;使用循环猜&#xff0c;一直猜中为止…

动态规划数字三角形模型——AcWing 1015. 摘花生

动态规划数字三角形模型 定义 动态规划数字三角形模型是在一个三角形的数阵中&#xff0c;通过一定规则找到从顶部到底部的最优路径或最优值。 运用情况 通常用于解决具有递推关系、需要在不同路径中做出选择以达到最优结果的问题。比如计算最短路径、最大和等 注意事项 …

OCR的有效数据增强

背景 我面临着需要尽可能准确识别手写金额的挑战。难点在于保持误判率低于0.01%。由于数据集中样本数量固定&#xff0c;因此数据增强是合乎逻辑的选择。快速搜索未发现针对光学字符识别&#xff08;OCR&#xff09;的现成方法。因此&#xff0c;我挽起袖子&#xff0c;亲自创建…

数据挖掘案例-商品零售购物篮分析

数据挖掘案例-商品零售购物篮分析 1. 背景与挖掘目标 现代商品种类繁多&#xff0c;顾客往往会由于需要购买的商品众多而变得疲于选择&#xff0c;且顾客并不会因为商品选择丰富而选择购买更多的商品。 例如&#xff0c;货架上有可口可乐和百事可乐&#xff0c;若顾客需要选…

「全新升级,性能更强大——ONLYOFFICE 桌面编辑器 8.1 深度评测」

文章目录 一、背景二、界面设计与用户体验三、主要新功能亮点3.1 高效协作处理3.2 共同编辑&#xff0c;毫无压力3.3 批注与提及3.4 追踪更改3.5 比较与合并3.6 管理版本历史 四、性能表现4.1 集成 AI 工具4.2 插件强化 五、用户反馈与使用案例 一、背景 Ascensio System SIA -…

服务器数据恢复—raid故障导致部分分区无法识别/不可用的数据恢复案例

服务器数据恢复环境&#xff1a; 一台某品牌DL380服务器中3块SAS硬盘组建了一组raid。 服务器故障&#xff1a; RAID中多块磁盘出现故障离线导致RAID瘫痪&#xff0c;其中一块硬盘状态指示灯显示红色。服务器上运行的数据库在D分区&#xff0c;备份文件存放在E分区。由于RAID瘫…

Git之checkout/reset --hard/clean -f区别(四十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

生命在于学习——Python人工智能原理(4.4)

三、Python的数据类型 3.2 Python的组合数据类型 特点&#xff1a;表示多个元素的组合&#xff0c;可以包含不同类型的元素&#xff0c;甚至是其他的组合数据类型。 在内存中通常需要额外的空间来存储元素间的关系。 组合数据类型能够将多个同类型或不同类型的数据组织起来&a…

Centos+Jenkins+Maven+Git 将生成的JAR部署到远程服务器上

1、登录 没有安装的参考下面的安装步骤先安装: Jenkins安装手册 输入账号、密码登录系统。 2、新建任务 2.1 创建页面 1,“输入一个任务名称”; 2,任务类型点击“构建一个maven项目”; 3,点击“确定”,此时,构建任务创建完成。 2.2 General 1、描述:输入要部署…

热电发电机越来越受到研发关注

热电发电机 (TEG) 利用热量&#xff08;或更准确地说&#xff0c;温差&#xff09;和众所周知的塞贝克效应来发电。它们的应用范围从收集可用热能&#xff0c;尤其是在工业和其他情况下“浪费”的热能&#xff0c;到在放射性同位素热发电机 (RTG) 中使用航天器的放射性电源作为…

day45--RocketMQ(三)

1. 高级功能 1.1 消息存储 分布式队列因为有高可靠性的要求&#xff0c;所以数据要进行持久化存储。 消息生成者发送消息MQ收到消息&#xff0c;将消息进行持久化&#xff0c;在存储中新增一条记录返回ACK给生产者MQ push 消息给对应的消费者&#xff0c;然后等待消费者返回A…

离镜头5cm也能拍清?Pura 70 超聚光微距如何做到“贴脸拍摄”?

虽然微距摄影在手机上已经算不得什么新鲜的功能&#xff0c;但要把微距摄影拍出高质量的效果&#xff0c;还是具有挑战性的。 众所周知&#xff0c;在微距摄影领域&#xff0c;镜头离被拍摄物品越近&#xff0c;照片的解析力和细节就越突出。但对于器件来讲&#xff0c;对焦距离…

年入百万不是梦?小米汽车员工晒收入,揭秘行业高薪背后的真相!

近日&#xff0c;社交媒体上出现了一位小米汽车员工的“凡尔赛”发言&#xff0c;其晒出的收入水平引发了网友们的热议。 这份令人艳羡的薪资条&#xff0c;也让“小米汽车待遇”迅速登上了热搜榜。究竟是什么样的魔力&#xff0c;让这家造车新势力能够开出如此优渥的条件&…

linux与windows环境下qt程序打包教程

一、演示环境 qt5.14.2 二、Linux 2.1 关联依赖文件 2.1.1 下载打包工具 在Windows环境下可以使用 Qt Creator自带的官方工具进行打包&#xff0c;而Linux环境下没有官方工具&#xff0c;需要借助第三方工具才能打包。如&#xff1a;linuxdeployqt、CQtDeployer、AppImage…

薄冰英语语法学习--名词1-不规则的

昨天学了&#xff0c;规则的&#xff0c;就是加es&#xff0c;或者变y为i加es&#xff0c;以及加s,还有变f和fe为v加es 今天学不规则。不规则就是完全没有规则&#xff0c;和s和es没有关系。就写死了告诉你&#xff0c;这个词的复数就是这样写。要硬背的。 首先来自古代英语的…