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

第十九天,二叉树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的父节点,或者已经找到了最近公共祖先节点,此时我们就需要不断的把结果进行返回。

总结 

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

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

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

相关文章

一文理清OCR的前世今生

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

CCSK认证考试详解:内容、形式、费用及备考指南

CCSK认证考试&#xff0c;也称为CCSK考试&#xff0c;是关于云计算安全的专业认证&#xff0c;由国际云安全联盟&#xff08;Cloud Security Alliance, CSA&#xff09;推出。以下是关于CCSK认证考试的详细信息&#xff1a; 考试内容 CCSK考试内容涵盖了云安全的基础知识&…

Day4: 两两交换链表中的节点 24 删除链表的倒数第N个节点 19 链表相交 02.07 环形链表II 142

题目24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* …

安泰电压放大器有什么作用

电压放大器是一种电子器件&#xff0c;它能够将输入信号的电压增大到所需的输出电压水平。电压放大器在电子电路设计中起到了至关重要的作用&#xff0c;下面将详细介绍电压放大器的作用。 信号放大作用&#xff1a;电压放大器主要作用是放大输入信号的电压&#xff0c;使其具有…

全域运营系统是如何做好全平台运营?

当前&#xff0c;全域运营的热度持续上涨&#xff0c;多篇分析全域运营平台优缺点的帖子也在多个创业者交流群中风靡一时。综合来看&#xff0c;在众多的全域运营平台中&#xff0c;属后面我们说的这家全域运营平台的分析最为详尽。 其中&#xff0c;对于我们的全域运营平台的优…

达梦数据库(DM8)替换授权dm.key遇到的错误, lic info is different between dm.key and sysinfo.

1、报错贴图 2、报错日志提示 version info: security lic info is different between dm.key and sysinfo. 原因说明&#xff1a;dm.key授权与服务器的硬件环境不匹配引起的报错&#xff0c;如&#xff1a;cpu、操作系统版本有关。

2023国家最高科学技术奖薛其坤院士:科学家的幸福感来自于哪里

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨沛贤 深度好文&#xff1a;2000字丨8分钟阅读 6 月 24 日&#xff0c;2023 年度国家最高科学技术奖在京揭晓&#xff0c;薛其坤院士荣获中国科技界崇高荣誉&#xff0c;这不…

【Python可视化系列】一文教你绘制带误差线的折线图(案例+源码)

这是我的第308篇原创文章。 一、引言 在日常工作或者科研任务时&#xff0c;经常会通过绘制折线图研究数据的变化趋势&#xff0c;有时候需要在折线图的基础上绘制误差线&#xff08;标准差范围&#xff09;&#xff0c;本文通过一个具体的案例教你如何实现绘制带误差线的折线…

mybatis x插件的使用教程(详细)

MyBatisX 的主要功能 代码生成&#xff1a; 自动生成 MyBatis 的 Mapper、XML 配置文件和实体类&#xff0c;大大减少手工编写代码的工作量。 智能代码补全&#xff1a; 提供 SQL 语句和 MyBatis 配置的智能代码补全功能&#xff0c;使开发者能够更快地编写代码。 代码导航&…

深度学习 —— 1.单一神经元

深度学习初级课程 1.单一神经元2.深度神经网络3.随机梯度下降法4.过拟合和欠拟合5.剪枝、批量标准化6.二分类 前言 本套课程仍为 kaggle 课程《Intro to Deep Learning》&#xff0c;仍按之前《机器学习》系列课程模式进行。前一系列《Keras入门教程》内容&#xff0c;与本系列…

<电力行业> - 《第1课:电力行业的五大四小》

1 什么是电力行业的五大四小&#xff1f; 我们常说的电力行业的五大四小&#xff0c;指的是电力行业有实力的公司&#xff0c;分为&#xff1a;较强梯队的五大集团、较弱梯队的四小豪门。 五个实力雄厚的集团&#xff0c;分别是&#xff1a; 中国华能集团公司中国大唐集团公…

Gemalto加密狗的优势

Gemalto加密狗&#xff0c;作为硬件加密技术的杰出代表&#xff0c;为软件开发商和用户提供了一种高效、安全的解决方案。这种加密狗不仅拥有卓越的加密性能&#xff0c;还具备易用性和可靠性&#xff0c;是保护软件知识产权和防止非法复制的重要工具。 一、Gemalto加密狗的核心…

玄奘取经线路矢量图分享

我们在《透过丝绸之路&#xff0c;看古人都走过哪些地方》一文中&#xff0c;为你分享过丝绸之路的矢量图数据。 现在&#xff0c;我们再为你分享一下玄奘取经线路的矢量图&#xff0c;你可以在文末查看这些数据的领取方式。 玄奘取经线路 《西游记》的故事相信大家都不陌生…

点在多边形内的判断

利用三角形相似. d e l t a L a t 1 d e l t a L a t 2 t a r g e t L o n d e l t a L o n \frac{deltaLat1}{deltaLat2} \frac{targetLon}{deltaLon} \\ deltaLat2deltaLat1​deltaLontargetLon​ t a r g e t L o n d e l t a L a t 1 d e l t a L o n d e l t a L a t…

洛谷 P10584 [蓝桥杯 2024 国 A] 数学题(整除分块+杜教筛)

题目 思路来源 登录 - Luogu Spilopelia 题解 参考了两篇洛谷题解&#xff0c;第一篇能得出这个式子&#xff0c;第二篇有比较严格的复杂度分析 结合去年蓝桥杯洛谷P9238&#xff0c;基本就能得出这题的正确做法 代码 #include<bits/stdc.h> #include<iostream&g…

中国 AGI 市场—4543 亿市场下的新机会

前言 我们正站在一个全新智能纪元的路口&#xff0c;围绕通用人工智能&#xff08;AGI&#xff09;&#xff0c;在学术界、科技界、产业界的讨论中&#xff0c;一部分 AGI 的神秘面纱已被揭开&#xff0c;但这面纱之后还有更多的未知等待着我们。 InfoQ 研究中心在此背景下&a…

示波器探头口碑性价比好的品牌有哪些推荐

示波器探头作为测试测量设备中的重要组成部分&#xff0c;市场上存在多个知名品牌。以下是一些主要的示波器探头品牌及其相关信息&#xff1a; Pintech品致&#xff1a;作为全球示波器探头第一品牌&#xff0c;Pintech品致是示波器探头技术标准倡导者&#xff0c;以及“两点浮…

【已解决】ModuleNotFoundError: No module named ‘_tkinter‘

由于网络上大多文章都是有关No module named tkinter’的问题&#xff0c;而没有实质性解决_tkinter找不到的问题。注意&#xff1a;这两个报错是不同的&#xff01;&#xff01;&#xff01; 对于No module named _tkinter问题&#xff0c;如果你使用了网络上大部分方法都不适…

利用opencv自带的Haar级联分类器模型

OpenCV自带的Haar级联分类器模型&#xff1a; haarcascade_eye.xml: 这个模型用于检测眼睛。 haarcascade_eye_tree_eyeglasses.xml: 这个模型用于检测眼镜。 haarcascade_frontalcatface.xml: 这个模型用于检测猫脸。 haarcascade_frontalcatface_extended.xml: 这个模型用…

数字化转型的难点在哪里?该如何突破?

我先把结论抛出来&#xff1a;数字化转型的难点不在于“数字化”&#xff0c;而在于“转型”。 如何理解这句话呢&#xff1f; 如果你此前做过数字化转型&#xff0c;想必也都清楚这一点&#xff0c;即&#xff1a;“数字化”解决的是生产工具的升级换代问题&#xff0c;“转…