24暑假算法刷题 | Day18 | LeetCode 530. 二叉搜索树的最小绝对差,501. 二叉搜索树中的众数,236. 二叉树的最近公共祖先

news2024/9/22 11:37:51

目录

  • 530. 二叉搜索树的最小绝对差
    • 题目描述
    • 题解
  • 501. 二叉搜索树中的众数
    • 题目描述
    • 题解
  • 236. 二叉树的最近公共祖先
    • 题目描述
    • 题解


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

点此跳转题目链接

题目描述

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值

差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

在这里插入图片描述

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

示例 2:

在这里插入图片描述

输入:root = [1,0,48,null,null,12,49]
输出:1

提示:

  • 树中节点的数目范围是 [2, 104]
  • 0 <= Node.val <= 105

题解

暴力解法把所有节点两两相减、求绝对值最小的差即可,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

我们可以利用二叉搜索树的一个重要性质来优化算法:

  • 二叉搜索树的中序遍历结果是一个从小到大的有序数列

此处默认节点左子树中各节点值小于节点、右子树中各节点值大于节点,则上面的性质是显然的。

也就是说,如果我们先获得中序遍历结果,再从头到尾依次两两相减,只需要遍历一次就可以得出最小绝对差。

由于中序遍历结果是递增序列,最小的任意两数之差肯定出现在相邻的两数之间;且后一个数大于前一个数,所以总让后一个数减去前一个数,所得结果必为正数,不必再求绝对值。

显然,这一算法可以直接在中序遍历的过程中运行,C++代码如下:

int res = INT_MAX;
TreeNode *pre; // 暂存中序遍历时前一个节点的指针

void traversal(TreeNode *root)
{
    if (root->left)
        traversal(root->left); // 左
    if (pre)
        res = min(res, root->val - pre->val); // 中
    pre = root;                               // 更新前指针
    if (root->right)
        traversal(root->right); // 右
}

int getMinimumDifference(TreeNode *root)
{
    traversal(root);
    return res;
}

501. 二叉搜索树中的众数

点此跳转题目链接

题目描述

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

在这里插入图片描述

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

示例 2:

输入:root = [0]
输出:[0]

提示:

  • 树中节点的数目在范围 [1, 104]
  • -105 <= Node.val <= 105

进阶: 你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

题解

既然是找众数,那么将二叉树遍历一遍、记录每个数出现的频率,是必不可少的了。常规思路应该是:

  • 遍历该树,同时记录节点值的出现频率

  • 找到最大的出现频率

  • 将出现频率最大的数加入结果集

    ⚠️ 注意题目说了,众数可能不止一个

当然,后两步可以合并:当发现更大的频率的时候,先将现有结果集清空,再加入新的最高频数字。这种方法的C++代码如下:

class Solution
{
public:
    unordered_map<int, int> freqMap; // 用一个哈希表记录每个数值出现的频率
    int maxShowTime = -1;
    
    // 中序遍历
    void traversal(TreeNode *cur)
    {
        if (cur->left)
            traversal(cur->left); // 左
        freqMap[cur->val]++;      // 中
        if (cur->right)
            traversal(cur->right); // 右
    }

    vector<int> findMode(TreeNode *root)
    {
        traversal(root);
        vector<int> res;
        for (const auto &pair : freqMap)
        {
            if (pair.second > maxShowTime)
            {
                maxShowTime = pair.second;
                res.clear(); // 出现更高频的元素,将原来的结果集清空
                res.push_back(pair.first);
            }
            else if (pair.second == maxShowTime)
                res.push_back(pair.first); // 众数可能不止一个
        }
        return res;
    }
};

不过,上述方法显然对任意树是通用的,意味着我们并没有利用到二叉搜索树的独特性质。同时,题目进阶要求也说了,可以不使用额外空间解决此题(上面的算法有个额外的哈希表空间开销)。

即是二叉搜索树,又要遍历之,自然联想到性质:

  • 二叉搜索树的中序遍历结果是一个从小到大的有序数列

所以,我们可以采用中序遍历的方法:

  • 如果当前节点值和上一个节点值相同,则当前频率加1;否则,说明遍历到一个新数值的节点,将当前频率重置为1
  • 如果当前频率等于最大频率,则将当前节点值加入结果集
  • 如果当前频率大于最大频率,则更新最大频率,清空原来结果集,将当前节点值接入结果集

这样,就避免了额外的空间开销:

class Solution
{
public:
    int maxShowTime = -1;
    int curShowTime = 0;
    TreeNode *pre = nullptr; // 中序遍历序列中,当前节点的前一个节点
    vector<int> res;
    // 中序遍历
    void traversal(TreeNode *cur)
    {
        // 左
        if (cur->left)
            traversal(cur->left); 
        
        // 中
        if (!pre)
            curShowTime = 1; // 第一个节点
        else if (cur->val == pre->val)
            curShowTime++; // 相同的节点值
        else
            curShowTime = 1; // 新的节点值
        
        pre = cur; // 更新前节点指针

        if (curShowTime == maxShowTime)
            res.push_back(cur->val);
        
        if (curShowTime > maxShowTime) {
            maxShowTime = curShowTime; // 更新最大出现频率
            res.clear();               // 清除之前的结果
            res.push_back(cur->val);
        }

        // 右
        if (cur->right)
            traversal(cur->right);
    }

    vector<int> findMode(TreeNode *root)
    {
       traversal(root);
       return res;
    }
};

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

点此跳转题目链接

题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:

在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

提示:

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同
  • p != q
  • pq 均存在于给定的二叉树中。

题解

根据题目描述,可以发现,符合直觉的思路是自底向上地查找——而这与后序遍历的遍历顺序相符。所以,我们考虑基于后序遍历,递归地返回 pq 的最近公共祖先。

用递归,首先考虑递归出口。我们想要返回的总是一个 p 和/或 q 的祖宗节点,所以以当前节点 root 的左右子树中必须有 p 和/或 q (自然,也有其祖宗节点),否则返回空节点:

if (!left && !right)
    return nullptr;

按照这种思路,节点不为空即说明该节点是 pq 或者它们之一/公共的祖宗节点

其余情况,即 root 的左右子树不都为空,说明它们之中有 p 和/或 q 及其祖宗节点:

1️⃣ 先看看最简单的情况:类似上面的示例1,当前节点 root 的左孩子 leftright 恰好就是 pq (对应关系无所谓),则 root 就是它们的最近公共祖先,此时返回 root 即可:

if (left && right)
    return root;

2️⃣ 如果当前节点是 pq ,也直接返回当前节点即可:题目说了 一个节点也可以是它自己的祖先

if (root == p || root == q)
    return root;

3️⃣ 如果 leftright 之间有且仅有一个不为空,则返回不为空的那个节点——该节点是 pq 或它们之一/公共的祖先:

相当于上面示例2中的节点 2

if (left && !right)
    return left;
if (!left && right)
    return right;

整体C++代码如下:

TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q)
{
    // 基于后序遍历
    if (!root)
        return nullptr;

    TreeNode *left = lowestCommonAncestor(root->left, p, q);   // 左
    TreeNode *right = lowestCommonAncestor(root->right, p, q); // 右

    // 中
    // 当前节点为p, q之一
    if (root == p || root == q)
        return root;
    // 否则,看左右孩子节点的情况
    else if (left && right)
        return root;
    else if (left && !right)
        return left;
    else if (!left && right)
        return right;
    else // 左右都为空
        return nullptr;
}

该算法更详细的讲解参见 代码随想录的本题题解 。

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

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

相关文章

Flink调优详解:案例解析(第42天)

系列文章目录 一、Flink-任务参数配置 二、Flink-SQL调优 三、阿里云Flink调优 文章目录 系列文章目录前言一、Flink-任务参数配置1.1 运行时参数1.2 优化器参数1.3 表参数 二、Flink-SQL调优2.1 mini-batch聚合2.2 两阶段聚合2.3 分桶2.4 filter去重&#xff08;了解&#xf…

Nvidia Isaac Sim代码编程 入门教程 2024(7)

Nvidia Isaac Sim 入门教程 2024 版权信息 Copyright 2023-2024 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. …

mac二进制安装operator-sdk

0. 前置条件 1. 安装go 安装步骤略。 1. 下载operator-sdk源码包 https://github.com/operator-framework/operator-sdk 1.1 选择适合当前go版本的operator版本&#xff0c;在operator-sdk/go.mod文件中可以查看Operator-sdk使用的go版本。 2. 编译 源码包下载后&#x…

冒泡,选择,插入,希尔排序

目录 一. 冒泡排序 1. 算法思想 2. 时间复杂度与空间复杂度 3. 代码实现 二. 选择排序 1. 算法思想 2. 时间复杂度与空间复杂度 3. 代码实现 三.插入排序 1. 直接插入排序 (1). 算法思想 (2). 时间复杂度与空间复杂度 (3). 代码实现 2. 希尔排序 (1). 算法思想 …

MongoDB教程(十五):MongoDB原子操作

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

2、如何发行自己的数字代币(truffle智能合约项目实战)

2、如何发行自己的数字代币&#xff08;truffle智能合约项目实战&#xff09; 1-Atom IDE插件安装2-truffle tutorialtoken3-tutorialtoken源码框架分析4-安装openzeppelin代币框架&#xff08;代币发布成功&#xff09; 1-Atom IDE插件安装 正式介绍基于web的智能合约开发 推…

netty 自定义客户端连接池和channelpool

目录标题 客户端池化运行分析问题修复 客户端池化 通信完成之后&#xff0c;一般要关闭channel&#xff0c;释放内存。但是与一个服务器频繁的打开关闭浪费资源。 通过连接池&#xff0c;客户端和服务端之间可以创建多个 TCP 连接&#xff0c;提升消息的收发能力&#xff0c;同…

PyTorch张量索引

文章目录 1、简介1.1、基本概念1.2、索引类型1.3、数据准备1.4、技术摘要⭐ 2、简单行、列索引3、列表索引4、范围索引5、布尔索引6、多维索引 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#…

Golang | Leetcode Golang题解之第241题为运算表达式设计优先级

题目&#xff1a; 题解&#xff1a; const addition, subtraction, multiplication -1, -2, -3func diffWaysToCompute(expression string) []int {ops : []int{}for i, n : 0, len(expression); i < n; {if unicode.IsDigit(rune(expression[i])) {x : 0for ; i < n &…

大规模优化问题,Scipy?Ceres?PyTorch!

背景&#xff1a; 优化问题一般通过scipy.optimize或者Ceres Solver优化器求解。但在参数量较大的优化问题上&#xff0c;scipy提供的BFGS、L-BFGS-B、CG、SLSQP等梯度优化算法其复杂度和存储需求指数级上升&#xff0c;无法满足计算效率&#xff1b;而Ceres需要额外的语言来支…

科普文:百度交易中台之系统对账篇

百度交易中台作为集团移动生态战略的基础设施&#xff0c;面向收银交易与清分结算场景&#xff0c;赋能业务、提供高效交易生态搭建。目前支持百度体系内多个产品线&#xff0c;主要包括&#xff1a;度小店、小程序、地图打车、文心一言等。本文主要介绍了百度交易中台的交易链…

如何让主机显示Docker容器的程序界面,同时支持声音播放

系统中如果安装各种应用软件&#xff0c;很容易会因为版本冲刺引发异常。一个好的办法就是用容器来隔离系统环境&#xff0c;确保主机环境不变。对于一些有界面的程序&#xff0c;可以在容器内运行&#xff0c;让其界面显示在主机上。下面以安装和使用视频剪辑软件shotcut为例&…

【一刷《剑指Offer》】面试题 42:翻转单词顺序 VS 左旋转字符串

力扣对应题目链接&#xff1a;151. 反转字符串中的单词 - 力扣&#xff08;LeetCode&#xff09; 牛客对应题目链接&#xff1a;翻转单词序列_牛客题霸_牛客网 (nowcoder.com) 核心考点 &#xff1a;子串划分&#xff0c;子串逆置。 一、题目一 1、《剑指Offer》对应内容 2、…

Delphi5实现加密程序

效果图 平面效果图 实现“确认按钮”和“加密” //点击确认输入按钮 procedure TForm1.btn1Click(Sender: TObject); //加密部分 varpasswd_2,passwd_3:string;beginpasswd_2:edt1.Text;Delete(passwd_2,3,2);passwd_3:mima;Delete(passwd_3,3,2);if(passwd_2passwd_3) thenM…

MAE(论文阅读):Masked Autoencoders are scalable vision learners

Masked Autoencoders Are Scalable Vision Learners 研究问题&#xff1a; 本文主要介绍了掩码自编码器( MAE, Masked autoencoders)是视觉领域中可扩展的自监督学习算法。MAE具体操作为随机屏蔽输入image中的patchs&#xff0c;再重建丢失的像素。其基于两个核心操作。第…

HTML5大作业三农有机,农产品,农庄,农旅网站源码

文章目录 1.设计来源1.1 轮播图页面头部效果1.2 栏目列表页面效果1.3 页面底部导航效果 2.效果和源码2.1 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_4…

浅谈Canal原理

canal [kə’nl]&#xff0c;译意为水道/管道/沟渠&#xff0c;主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据 订阅 和 消费。应该是阿里云DTS&#xff08;Data Transfer Service&#xff09;的开源版本。 Canal与DTS提供的功能基本相似&#xff1a; 基于…

python Requests库7种主要方法及13个控制参数(实例实验)

文章目录 一、Requests库的7种主要方法二、kwargs:控制访问的13个参数 一、Requests库的7种主要方法 序号方法说明1requests.request()&#xff1a;提交一个request请求&#xff0c;作为其他请求的基础2requests.get()&#xff1a;获取HTML网页代码的方法3requests.head()&…

内网隧道——隧道技术基础

文章目录 一、正向连接与反向连接1.1 正向连接1.2 反向连接 二、端口转发三、端口映射四、端口复用五、代理和隧道的区别六、常见隧道穿透分类 环境&#xff1a; kali&#xff1a;192.168.92.6&#xff0c;MSF v6.3.25 win7&#xff1a;192.168.92.7 一、正向连接与反向连接 1…

python实现误差扩散、Floyd-Steinberg 抖动、有序抖动、Riemersma 抖动算法

误差扩散、Floyd-Steinberg 抖动、有序抖动、Riemersma 抖动算法 1.误差扩散算法详解算法步骤Floyd-Steinberg 算法公式Python 实现详细解释优缺点 2.有序抖动算法详解算法步骤Bayer矩阵公式Python 实现详细解释优缺点 3.Riemersma 抖动算法详解算法步骤公式Python 实现详细解释…