算法训练第五十八天 | 739. 每日温度、496.下一个更大元素 I

news2024/11/19 0:43:14

单调栈part01

  • 739. 每日温度
    • 题目描述
    • 思路
  • 496.下一个更大元素 I
    • 题目描述
    • 思路

739. 每日温度

题目链接:739. 每日温度
参考:https://programmercarl.com/0739.%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.html

题目描述

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

思路

首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)

那么接下来在来看看使用单调栈的解法。

那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。

例如本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。

那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?

单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。

在使用单调栈的时候首先要明确如下几点:

  1. 单调栈里存放的元素是什么?

单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

  1. 单调栈里元素是递增呢? 还是递减呢?

注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。

这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。

即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程,大家再去思考,本题为什么是递增栈。

使用单调栈主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

把这三种情况分析清楚了,也就理解透彻了。

接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

首先先将第一个遍历元素加入单调栈
在这里插入图片描述
加入T[1] = 74,因为T[1] > T[0](当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况)。

我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出,T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。
在这里插入图片描述
加入T[2],同理,T[1]弹出
在这里插入图片描述
加入T[3],T[3] < T[2] (当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况),加T[3]加入单调栈。
在这里插入图片描述
加入T[4],T[4] == T[3] (当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于!
在这里插入图片描述
加入T[5],T[5] > T[4] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[4]弹出,同时计算距离,更新result
在这里插入图片描述
T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[3]继续弹出,同时计算距离,更新result
在这里插入图片描述
直到发现T[5]小于T[st.top()],终止弹出,将T[5]加入单调栈
在这里插入图片描述
加入T[6],同理,需要将栈里的T[5],T[2]弹出
在这里插入图片描述
同理,继续弹出
在这里插入图片描述
此时栈里只剩下了T[6]
在这里插入图片描述
加入T[7], T[7] < T[6] 直接入栈,这就是最后的情况,result数组也更新完了。
在这里插入图片描述
此时有同学可能就疑惑了,那result[6] , result[7]怎么没更新啊,元素也一直在栈里。

其实定义result数组的时候,就应该直接初始化为0,如果result没有更新,说明这个元素右面没有更大的了,也就是为0。

以上在图解的时候,已经把,这三种情况都做了详细的分析。

情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
通过以上过程,大家可以自己再模拟一遍,就会发现:只有单调栈递增(从栈口到栈底顺序),就是求右边第一个比自己大的,单调栈递减的话,就是求右边第一个比自己小的。

C++代码如下:

// 版本一
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        // 递增栈
        stack<int> st;
        vector<int> result(T.size(), 0);
        st.push(0);
        for (int i = 1; i < T.size(); i++) {
            if (T[i] < T[st.top()]) {                       // 情况一
                st.push(i);
            } else if (T[i] == T[st.top()]) {               // 情况二
                st.push(i);
            } else {
                while (!st.empty() && T[i] > T[st.top()]) { // 情况三
                    result[st.top()] = i - st.top();
                    st.pop();
                }
                st.push(i);
            }
        }
        return result;
    }
};

建议一开始 都把每种情况分析好,不要上来看简短的代码,关键逻辑都被隐藏了。

精简代码如下:

// 版本二
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        stack<int> st; // 递增栈
        vector<int> result(T.size(), 0);
        for (int i = 0; i < T.size(); i++) {
            while (!st.empty() && T[i] > T[st.top()]) { // 注意栈不能为空
                result[st.top()] = i - st.top();
                st.pop();
            }
            st.push(i);

        }
        return result;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。

建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简!

496.下一个更大元素 I

题目链接:496.下一个更大元素 I
参考:https://programmercarl.com/0496.%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0I.html

题目描述

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:

  • 输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
  • 输出: [-1,3,-1]
  • 解释:
    对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
    对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
    对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

示例 2:

  • 输入: nums1 = [2,4], nums2 = [1,2,3,4].
  • 输出: [3,-1]
  • 解释:
    对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
    对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。

提示:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 10^4
  • nums1和nums2中所有整数 互不相同
  • nums1 中的所有整数同样出现在 nums2 中

思路

在739. 每日温度 中是求每个元素下一个比当前元素大的元素的位置。

本题则是说nums1 是 nums2的子集,找nums1中的元素在nums2中下一个比当前元素大的元素。

几乎是一样的,但是这么绕了一下,其实还上升了一点难度。

需要对单调栈使用的更熟练一些,才能顺利的把本题写出来。

从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。

这么定义这个result数组初始化应该为多少呢?

题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1。

在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。

注意题目中说是两个没有重复元素 的数组 nums1 和 nums2。

没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。

C++中,当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的。

那么预处理代码如下:

unordered_map<int, int> umap; // key:下标元素,value:下标
for (int i = 0; i < nums1.size(); i++) {
    umap[nums1[i]] = i;
}

使用单调栈,首先要想单调栈是从大到小还是从小到大。

本题和739. 每日温度是一样的。

栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。

接下来就要分析如下三种情况,一定要分析清楚。

  • 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
    此时满足递增栈(栈头到栈底的顺序),所以直接入栈。
  • 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
    如果相等的话,依然直接入栈,因为我们要求的是右边第一个比自己大的元素,而不是大于等于!
  • 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
    此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。
    判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。
    记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2数组中右面第一个大的元素是nums2[i](即当前遍历元素)。

代码如下:

while (!st.empty() && nums2[i] > nums2[st.top()]) {
    if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
        int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标
        result[index] = nums2[i];
    }
    st.pop();
}
st.push(i);

以上分析完毕,C++代码如下:

// 版本一
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        stack<int> st;
        vector<int> result(nums1.size(), -1);
        if (nums1.size() == 0) return result;

        unordered_map<int, int> umap; // key:下标元素,value:下标
        for (int i = 0; i < nums1.size(); i++) {
            umap[nums1[i]] = i;
        }
        st.push(0);
        for (int i = 1; i < nums2.size(); i++) {
            if (nums2[i] < nums2[st.top()]) {           // 情况一
                st.push(i);
            } else if (nums2[i] == nums2[st.top()]) {   // 情况二
                st.push(i);
            } else {                                    // 情况三
                while (!st.empty() && nums2[i] > nums2[st.top()]) {
                    if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
                        int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标
                        result[index] = nums2[i];
                    }
                    st.pop();
                }
                st.push(i);
            }
        }
        return result;
    }
};

针对版本一,进行代码精简后,代码如下:

// 版本二
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        stack<int> st;
        vector<int> result(nums1.size(), -1);
        if (nums1.size() == 0) return result;

        unordered_map<int, int> umap; // key:下标元素,value:下标
        for (int i = 0; i < nums1.size(); i++) {
            umap[nums1[i]] = i;
        }
        st.push(0);
        for (int i = 1; i < nums2.size(); i++) {
            while (!st.empty() && nums2[i] > nums2[st.top()]) {
                if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
                    int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标
                    result[index] = nums2[i];
                }
                st.pop();
            }
            st.push(i);
        }
        return result;
    }
};

精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。

建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简!

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

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

相关文章

Android组件化开发

Android组件开发 一、背景 一个app随着业务增加&#xff0c;代码放在同一个模块中会越来越臃肿&#xff0c;同时也导致多人开发的一个难度。组件化可以把业务单独分出来&#xff0c;形成一个单独模块&#xff0c;可单独运行、测试等&#xff0c;相互之间不会影响。另外一个优…

鼎捷T100制造之工艺工单实战(其他工艺补充)

文章目录 一、网状工艺二、平行工艺三、替代工艺四、返工工艺五、无顺序工艺一、网状工艺 网状工艺类似一张网状结构。可以包含平行和线性工艺等于一体。 网状工艺: 产品结构 aeci004:建立作业 aecm200: 工艺路线维护

kubeasz搭建k8s集群-部署单节点集群(AllinOne部署)

1说明 kubeasz 致力于提供快速部署高可用k8s集群的工具, 同时也努力成为k8s实践、使用的参考书&#xff1b;基于二进制方式部署和利用ansible-playbook实现自动化&#xff1b;既提供一键安装脚本, 也可以根据安装指南分步执行安装各个组件。 kubeasz 从每一个单独部件组装到完…

太赫兹高速通信系统前端关键技术

摘要&#xff1a;对构成太赫兹无线系统的2 种关键电路&#xff08;分谐波混频器和二倍频器&#xff09;进行了深入研究。在关键电路研究取得突破的基础上&#xff0c;开展了太赫兹无线通信技术研究&#xff0c;构建了220 GHz 无线通信实验验证系统。220 GHz 实验验证系统在室外…

elasticsearch 其他字段类型详解和范例

本章主要内容 elasticsearch 中别名字段的详解和范例elasticsearch 中二进制类型的详解和范例elasticsearch 中的嵌套类型的详解和范例elasticsearch 中的范围类型的详解和范例elasticsearch 中的排名类型的详解和范例elasticsearch 中的ip类型的详解和范例elasticsearch 中的…

【网络应用开发】实验3——Web组件重用与JavaBeans

目录 Web组件重用与JavaBeans预习报告 一、实验目的 二、实验原理 三、实验预习内容 1. 静态include指令何时执行&#xff1f;主页面和被包含的子页面是否转换为一个转换单元&#xff1f; 2.动作指令何时执行&#xff1f;主页面和被包含的子页面是否转换为一个转换单元&a…

【Python游戏】坦克大战、推箱子小游戏怎么玩?学会这些让你秒变高高手—那些童年的游戏还记得吗?(附Pygame合集源码)

前言 下一个青年节快到了&#xff0c;想小编我也是过不了几年节日了呢&#xff01;&#xff01; 社交媒体上流传着一张照片——按照国家规定“14岁到28岁今天都应该放半天假&#xff01;”不得不说&#xff0c; 这个跨度着实有点儿大&#xff0c;如果按整自然年来算年龄&…

Spark 连接 Hive

目录 导包 修改配置文件 修改hive-site.xml文件 启动hadoop 启动hive 启动spark 测试 查看表 导包 spark连接hive需要六个关键的jar包&#xff0c;以及将hive的配置文件hive-site.xml拷贝到spark的conf目录下。如果你hive配置没问题的话&#xff0c;这些jar都在hive的目…

Spring Security实战(四)—— 会话管理

目录 一、理解会话 二、防御会话固定攻击 三、会话过期 四、会话并发控制 五、集群会话的缺陷 六、集群会话的解决方案 七、整合Spring Session解决集群会话问题 只需在两个浏览器中用同一个账号登录就会发现&#xff0c;系统并没有任何会话并发限制。一个账号可以多处同…

cuSPARSE官方程序示例

cuSPARSE Library 简介 这个文件演示了cuSPARSE通用API的用法 官方程序&#xff1a;后续会出解析&#xff08;20230410&#xff09; cuSPARSE Generic APIs Documentation cuSPARSE Samples 向量 - 向量 操作矩阵 - 向量 操作矩阵 - 矩阵操作转换Legacy APIs优化稀疏迭代…

阿里本地生活再出发:口碑入高德,备战美团、抖音

配图来自Canva可画 近日&#xff0c;有传言称高德地图将和阿里本地生活旗下的到店业务口碑正式合并&#xff0c;未来阿里旗下所有的本地生活到店业务都将统一整合在高德地图的入口中。3月22日&#xff0c;高德地图正式确认了此事&#xff0c;并表示高德地图作为“出门好生活开…

网络软件-管理网络设备和组件

网络软件是一个术语&#xff0c;表示帮助网络管理员轻松执行特定操作的任何网络软件。术语“网络软件”可以包括广泛的软件解决方案&#xff0c;每个解决方案都提供特定的功能&#xff0c;共同帮助网络管理员完全控制其IT基础架构。网络软件工具提供监控网络运行状况、测量性能…

一个简单的MUX-VLAN实验(华为eNSP模拟器)

MUX-VLAN产生背景及用途 在数据中心网络中&#xff0c;数据中心管理员希望数据中心内部所有服务器&#xff08;或终端&#xff09;都可以访问外部网络&#xff0c;同时部分服务器之间可以互相通信&#xff0c;部分服务器之间相互隔离。一般情况下&#xff0c;为了实现所有服…

python贪吃蛇代码

文末用python写好了贪吃蛇代码可直接复制使用&#xff01; 今天和大家分享一下贪吃蛇的代码&#xff0c;主要是贪吃蛇的一些基本知识&#xff0c;以及怎么去玩。 首先&#xff0c;我们先了解一下什么是贪吃蛇&#xff1f;它是一个具有多种功能的游戏&#xff0c;可以通过点击屏…

绿联dx4600砖机救援

由于在操作ssh过程不当&#xff0c;导致成为搬砖&#xff0c;记录一下救援过程 涉及到拆机&#xff0c;会导致损保&#xff0c;建议邮寄官方救援 变砖前因&#xff1a; 发现可以使用opkg&#xff0c;遂用opkg进行安装包的操作&#xff0c;最后更新busy-box&#xff0c;而进行b…

Vue源码解析(尚硅谷)

视频地址&#xff1a;Vue源码解析系列课程 一、Vue源码解析之mustache模板引擎 1. 什么是模板引擎 模板引擎是将数据要变为视图最优雅的解决方案 历史上曾经出现的数据变为视图的方法 2. mustache基本使用 mustache库简介 mustache库基本使用 3. mustache的底层核心机理…

3.7.2数据库系统-数据库控制技术:数据库的安全性、数据库备份与恢复技术、数据备份、数据库故障与恢复、数据库性能优化

3.7.2数据库系统-数据库控制技术&#xff1a;数据库的安全性、数据库备份与恢复技术、数据备份、数据库故障与恢复、数据库性能优化数据库的安全性数据库备份与恢复技术数据备份数据库故障与恢复数据库性能优化数据库的安全性 在做信息系统开发的过程当中&#xff0c;数据库是…

集合的线程安全解读

目录 集合操作 Demo Vector Collections CopyOnWriteArrayList(重点) “动态数组”机制 “线程安全”机制 集合操作 Demo NotSafeDemo public class NotSafeDemo {/*** 多个线程同时对集合进行修改* param args*/public static void main(String[] args) {List list ne…

100Wqps异地多活,得物是怎么架构的?

说在前面 在40岁老架构师尼恩的数千读者群中&#xff0c;一直在指导大家简历和职业升级&#xff0c;前几天&#xff0c;指导了一个华为老伙伴的简历&#xff0c;小伙伴的优势在异地多活&#xff0c;但是在简历指导的过程中&#xff0c;尼恩发现&#xff1a; 异地多活的概念、异…

大专毕业该怎么办?

大专学历转IT拿10k&#xff01;谁说生活不能是热血漫&#xff1f; “本科生讨论孔乙己的长衫&#xff0c;我们专科生怎么办&#xff1f;” “专科生的唯一出路难道就是进厂吗&#xff1f;” 在如今的就业内卷浪潮中&#xff0c;专科生的处境是最为尴尬的。一方面&#xff0c;很…