数据结构算法 | 单调栈

文章目录

  • 算法概述
  • 题目
    • 下一个更大的元素 I
      • 思路
      • 代码
    • 下一个更大元素 II
      • 思路
      • 代码
    • 132 模式
      • 思路
      • 代码
    • 接雨水
      • 思路


算法概述

当题目出现 「找到最近一个比其大的元素」 的字眼时,自然会想到 「单调栈」 。——三叶姐

单调栈以严格递增or递减的规则将无序的数列进行选择性排序。


题目

下一个更大的元素 I

给你两个 没有重复元素 的数组 nums1nums2 ,其中 nums1nums2 的子集。(题源力扣)

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

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

示例:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:

  • 对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
  • 对于 num1 中的数字 1 ,第二个数组中数字 1 右边的下一个较大数字是 3 。
  • 对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

思路

  1. 倒序遍历 nums2stack 暂存严格递增的数据,即,若 栈不为空当前遍历到的元素大于栈顶元素 则弹出栈顶元素。
  2. unordred_map 存储 遍历到的元素(key最近一个比其大的元素(value 之间的关系。经过步骤一后,若栈不为空,则栈顶元素即为 value ,否则,value-1
  3. 正序遍历 nums1 并依赖 unordred_map 得到题目要求的结果数组。

代码

class Solution {
public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {unordered_map<int, int> m;stack<int> st;for(int i = nums2.size()-1; i >= 0; i--){int t = nums2[i];while(st.size() && t>st.top()) st.pop();m[t] = st.empty() ? -1 : st.top();st.push(t);}vector<int> v;for(int i = 0; i < nums1.size(); i++){v.push_back(m[nums1[i]]);}return v;}
};

下一个更大元素 II

给定一个循环数组,输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数。如果不存在,则输出 -1。(题源力扣)

示例:

输入: [1,2,1]
输出: [2,-1,2]
解释:

  1. 第一个 1 的下一个更大的数是 2;
  2. 数字 2 找不到下一个更大的数;
  3. 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

思路

因为我们要循环搜索,因此要么将数组拉直——复制所有元素并添加在数组尾部;要么假设数组长度为真正长度的二倍,并且遍历的时候对长度取余,这里我们选择后一种方法。并将遍历分成正序遍历和逆序遍历两种方法:

正序遍历:

  • 单调栈 st 用以记录下标,正序遍历给定的数组 nums,范围为 [0, nums.size()-1] ,当前遍历到的下标值为 i ,结果数组为 v ,结果数组数值全部被初始化为 -1
  • 栈不为空栈顶下标对应的元素 小于 当前遍历的元素
    1. v[栈顶下标] = nums[i%n]栈顶下标对应的元素下一个最大元素 即为 当前遍历到的元素
    2. st.pop() ,弹出栈顶元素,单调栈遵循严格递增规则。
  • 当前遍历到的元素其下标入栈,st.push(i%n)。由于每个元素都会被遍历到并且被压入栈中,而栈中每个元素都会被处理(没有下一个最大元素的栈中元素直接被弹出,反之则匹配下一个最大元素)。

逆序遍历:

  • 单调栈 st 用以记录元素值,逆序遍历给定的数组 nums,范围为 [nums.size()-1, 0] ,当前遍历到的下标值为 i ,结果数组为 v ,结果数组数值全部被初始化为 -1
  • 栈不为空栈顶元素 小于等于 当前遍历的元素
    1. st.pop() ,弹出栈顶元素,单调栈遵循严格递增规则。
  • i < nums.size() 时,开始逆序更新结果数组的元素,在此之前对单调栈进行的更新只为解决 nums 最后一次降峰的元素(下例中的 5,4 )单次遍历无法匹配下一个更大元素的问题。以 2,3,6,4,5,4 为例:
    1. 一次遍历只能得到结果数组 3,6,-1,5,-1,-1。对于 5,4 两个元素而言,无法正确匹配下一个更大元素,这也是和上一题的主要区别之所在。
    2. 因此倘若我们将整个遍历过程看作对 nums 的两次遍历,就会发现,第一次遍历结果是单调栈中只有 6,也就是解决了无法正确匹配5,4 两个元素的下一个更大元素,而第二次遍历则是对最后一次降峰之前所有元素进行匹配下一个最大元素的操作。

注:上面提到了降峰,这其实是对数组常见的称呼方法,意为将数组元素以折线图的形式表示看起来像一座座山峰,对于 2,3,6,4,5,4 而言,它们是形如下图的两座山峰:
在这里插入图片描述


代码

正序遍历:

class Solution {
public:vector<int> nextGreaterElements(vector<int>& nums) {int n = nums.size();vector<int> v(n, -1);stack<int> st;for(int i = 0; i < n*2; i++){int t = nums[i%n];while(st.size() && nums[st.top()]<t){v[st.top()] = t;st.pop();}st.push(i%n);//cout << nums[i%n] << " " << m[nums[i%n]] << endl;}return v;}
};

逆序遍历:

class Solution {
public:vector<int> nextGreaterElements(vector<int>& nums) {int n = nums.size();vector<int> v(n, -1);stack<int> st;for(int i = n*2-1; i >= 0; i--){int t = nums[i%n];while(st.size() && st.top()<=t) st.pop();if(i <= n-1) v[i] = st.empty() ? -1 : st.top();st.push(t);//cout << nums[i%n] << " " << m[nums[i%n]] << endl;}return v;}
};

132 模式

给你一个整数数组 nums ,数组中共有 n 个整数。132模式 的子序列 由三个整数 nums[i]nums[j]nums[k] 组成,并同时满足:i < j < knums[i] < nums[k] < nums[j] 。(题源力扣)

如果 nums 中存在 132模式 的子序列 ,返回 true ;否则,返回 false

示例:

输入: nums = [1,2,3,4]
输出: false
解释: 序列中不存在 132 模式的子序列。


思路

  • 单调栈 st 用以暂存可能为 132模式2 的数据,遵循严格递减规则,变量 sec 为真正的 2,将其初始化为 INT_MIN
  • 倒序遍历数组,因为我们目的在于枚举 2 ,而 2 必定从数组尾部优先出现,当前遍历到的下标为 i
    1. nums[i] < sec ,确定了 1,返回 true
    2. 栈不为空当前遍历到的元素 大于 栈顶元素 ,当前元素暂定为 3 ,更新 sec 代表的 2
    3. 优化:当前遍历到的元素 大于 sec ,当前元素入栈。本条规则的依据是:如果 nums[i] ≤ sec,那么即使它在未来被弹出,也不会将 sec 更新为更大的值。
      • 为什么 sec 更新为更大的值 如此重要呢?因为 2 的值越大,那么我们之后找到 1 的机会也就越大。

代码

class Solution {
public:bool find132pattern(vector<int>& nums) {int n = nums.size();stack<int> st; // 单调栈维护2st.push(nums[n-1]);int sec = INT_MIN; // second表示真正的2for(int i = n-2; i >= 0; i--){int t = nums[i];if(t < sec) return true; // 当前t作为1while(st.size() && t>st.top()){sec = st.top();st.pop();}if(t>sec) st.push(t); // 优化}return false;}
};

接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例:
在这里插入图片描述

输入: height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
解释: 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。


思路

单调栈 st 遵循严格递减规则,暂存可以作为 接水形状左边界的下标。用变量 res 保存能接多少雨水,正序遍历数组 height

  • 栈不为空当前遍历到的元素 大于 栈顶下标对应的元素,表明有可能出现可接雨水的 “容器” 。
    1. 将栈顶下标对应元素作为容器底部 bot,然后弹出,将新的栈顶下标作为容器左边界 left
    2. 容器能容纳的雨水为 长 * 高 = i - left - 1 * (min(height[left], height[i]) - bot) ,解释一下,长度为右边界 i 和 左边界 left 的 差值减一,高度为 左边界高度和右边界高度中较小值 与 容器底部 bot 的差值,毕竟木桶接水的多少取决于最短的一块木板。
  • 其余情况则直接将 当前遍历到的元素下标 压入栈中,此时单调栈 st 遵循严格递减规则。
class Solution {
public:int trap(vector<int>& height) {int n = height.size();stack<int> st;int res = 0;for(int i = 0; i < n; i++){int t = height[i];while(st.size() && height[st.top()]<t){int bot = height[st.top()];//cout << "left: " << left << endl;st.pop();if(st.size()){int left = st.top();res += (i-left-1)*(min(t, height[left])-bot);}}st.push(i);}return res;}
};

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

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

相关文章

最长下降子序列

文章目录题目解法DP暴搜思路代码实现贪心二分思路代码实现题目 给出一组数据 nums&#xff0c;求出其最长下降子序列&#xff08;子序列允许不连续&#xff09;的长度。&#xff08;类似于lc的最长递增子序列&#xff09; 示例&#xff1a; 输入&#xff1a; 6 // 数组元素个…

Linux 服务器程序规范、服务器日志、用户、进程间的关系

文章目录服务器程序规范日志rsyslogd 守护进程syslog函数openlog函数setlogmask函数closelog函数用户进程间的关系进程组会话系统资源限制改变工作目录和根目录服务器程序后台化服务器程序规范 Linux 服务器程序一般以后台进程&#xff08;守护进程[daemon]&#xff09;形式运…

IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO

文章目录IO模型阻塞IO非阻塞IO信号驱动IO多路复用IO异步IOIO模型 根据各自的特性不同&#xff0c;IO模型被分为阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO五类。 最主要的两个区别就是阻塞与非阻塞&#xff0c;同步与异步。 阻塞与非阻塞 阻塞与非阻塞最主要的区别就…

Tomcat服务器集群与负载均衡实现

一、前言 在单一的服务器上执行WEB应用程序有一些重大的问题&#xff0c;当网站成功建成并开始接受大量请求时&#xff0c;单一服务器终究无法满足需要处理的负荷量&#xff0c;所以就有点显得有点力不从心了。另外一个常见的问题是会产生单点故障&#xff0c;如果该服务器坏掉…

Linux服务器 | 事件处理模式:Reactor模式、Proactor模式

文章目录Reactor模式Proactor模式同步I/O模型模拟Proactor模式两者的优缺点ReactorProactor同步I/O模型通常用于实现 Reactor 模式&#xff0c;异步I/O模型通常用于实现 Proactor 模式。&#xff08;不是绝对的&#xff0c;同步I/O也可模拟出 Proactor 模式&#xff09; React…

Linux服务器 | 服务器模型与三个模块、两种并发模式:半同步/半异步、领导者/追随者

文章目录两种服务器模型及三个模块C/S模型P2P模型I/O处理单元、逻辑单元、存储单元并发同步与异步半同步/半异步模式变体&#xff1a;半同步/半反应堆模式改进&#xff1a;高效的半同步/半异步模式领导者/追随者模式组件 &#xff1a;句柄集、线程集、事件处理器工作流程两种服…

字符串匹配之KMP(KnuthMorrisPratt)算法(图解)

文章目录最长相等前后缀next数组概念代码实现图解GetNext中的回溯改进代码实现代码复杂度分析最长相等前后缀 给出一个字符串 ababa 前缀集合&#xff1a;{a, ab, aba, abab} 后缀集合&#xff1a;{a, ba, aba, baba} 相等前后缀 即上面用同样颜色标识出来的集合元素&#…

Android入门(一) | Android Studio的配置与使用

文章目录安装配置Android Studio使用Android Studio模拟器更改Android SDK的路径Hello World&#xff01;安装配置Android Studio 从这一步开始&#xff1a; 一直点 next 即可&#xff0c;直到存储路径的选择上&#xff0c;可以放到非 C 盘&#xff0c;这里我放到 D 盘了&am…

Android 入门(四) | Intent 实现 Activity 切换

文章目录Intent显式 Intent定义两个 xml 文件android:orientationmatch_parent 和 wrap_contentIntent函数定义两个 Activity隐式 Intent更多隐式 Intent 的用法用隐式 Intent 打开系统浏览器自建 Activity 以响应打开网页的 Intent向下一个活动传递数据返回数据给上一个活动In…

Android入门(二) | 项目目录及主要文件作用分析

文章目录项目目录分析app目录分析AndroidManifest.xml 分析MainActivity.kt 分析build.gradle 分析最外层目录下的 build.gradleapp 目录下的 build.gradle项目目录分析 我们来看一下 src/main/res 下的一些文件&#xff1a; .gradle 和 .idea &#xff1a;这两个目录下放置…

Android入门(三) | Android 的日志工具 Logcat

文章目录日志工具类 android.util.LogLogcat 中的过滤器日志工具类 android.util.Log Log 从属日志工具类 android.util.Log &#xff0c;该类提供了五个方法供我们打印日志&#xff1a; Log.v() &#xff1a;用于打印那些最为琐碎的、意义最小的日志信息。对应级别 verbose&…

Android入门(五) | Activity 的生命周期

文章目录Activity 的状态及生命周期实现管理生命周期FirstActivitySecondActivityDialogActivity运行结果旧活动被回收了还能返回吗&#xff1f;Activity 的状态及生命周期 Android 的应用程序运用 栈&#xff08;Back Stack&#xff09; 的思想来管理 Activity&#xff1a; …

Android入门(六) | Activity 的启动模式 及 生产环境中关于 Activity 的小技巧

文章目录Activity 的启动模式standardsingleTopsingleTasksingleInstance技巧了解当前界面是哪个 Activity随时随地退出程序启动活动的最佳写法Activity 的启动模式 standard&#xff1a;默认的启动方式&#xff0c;每次启动一个活动都会重新创建singleTop&#xff1a;如果该活…

Android入门(七) | 常用控件

文章目录TextView 控件&#xff1a;文本信息Button 控件&#xff1a;按钮EditText 控件&#xff1a;输入框ImageView 控件&#xff1a;图片ProgressBar 控件&#xff1a;进度条AlertDialog 控件&#xff1a;提示框ProgressDialog 控件&#xff1a;带有进度条的提示框TextView 控…

Android入门(八) | 常用的界面布局 及 自定义控件

文章目录LinearLayout &#xff1a;线性布局android:layout_gravity &#xff1a;控件的对齐方式android:layout_weight&#xff1a;权重RelativeLayout &#xff1a;相对布局相对于父布局进行定位相对于控件进行定位边缘对齐FrameLayout &#xff1a;帧布局Percent &#xff1…

Android入门(九)| 滚动控件 ListView 与 RecyclerView

文章目录ListView内置类型的简单运用定制数据类型提升效率点击事件RecyclerView布局管理器点击事件ListView 内置类型的简单运用 由于手机屏幕空间有限&#xff0c;能够一次性在屏幕上显示的内容不多&#xff0c;当我们的程序有大量数据需要显示的时候就可以借助 ListView 来…

Android入门(10)| Fragment碎片详解

文章目录为什么要使用碎片&#xff08;Fragment&#xff09;实例布局文件FragmentActivity动态添加碎片布局文件FragmentActivity碎片通信Fragment布局文件Activity生命周期为什么要使用碎片&#xff08;Fragment&#xff09; 我们在手机上看新闻可能是这样的&#xff1a; Re…

Android开发(1) | Fragment 的应用——新闻应用

文章目录Item&#xff1a;标题子项布局文件Java代码标题碎片布局文件Java代码新闻内容碎片布局文件Java代码新闻内容活动布局文件Java代码首界面布局文件Java代码Item&#xff1a;标题子项 布局文件 news_item.xml&#xff1a; <TextViewxmlns:android"http://schema…

Android入门(11)| 全局广播与本地广播

文章目录广播概念接收广播动态注册实例静态注册实例发送广播发送标准广播广播的跨进程特性发送有序广播本地广播广播概念 Android 中的每个应用程序都可以对自己感兴趣的广播进行注册&#xff0c;这样该程序就只会接收到自己所关心的广播内容&#xff0c;这些广播可能是来自系…

Android开发(2) | 广播 Broadcast 的应用——强制下线功能

文章目录功能简介关闭所有活动登陆界面发送强制下线的广播广播接收器AndroidManifest.xml运行结果功能简介 强制下线功能只需要弹出一个对话框&#xff0c;让用户只能点击确定按钮&#xff0c;回到登录界面。 如果在每一个活动中添加一个对话框的话太过繁琐&#xff0c;用广播…