[杂记]算法: 单调栈

news2024/12/30 2:45:40

0. 引言

单调栈, 顾名思义就是从栈底到栈顶元素单调递增或者单调递减的栈. 往往, 我们在解决寻找一个元素前面/后面的最远/最近处满足某条件的另一个元素的时候可以用到单调栈.

也是用两道算法题作为例子. 在这之前, 先简单写一下构造单调栈的模板.

如果我们需要从一个数组中组成一个单调栈, 不妨设从栈底到栈顶的元素逐级增加, 代码为:

stack<T> stk;

for (T item: arr) {
	if (stk.empty() || stk.top() < item)
		stk.push(item);
}

也就是单调栈提取了数组中的单调元素的规律, 而将不符合该规律的元素排除在外.

1. 表现良好的最长时间段

题目链接: LeetCode1124

这道题是说, 给定一个数组, 例如[9,9,6,0,6,6,9], 找到一个连续子数组, 使得这个子数组里大于8的元素要比小于等于8的元素多.

分析一下这个问题, 如果一个区间, 一开始有4个大于8的元素, 但后面跟了一个小于8的元素, 这时候一共有三个大于8的元素, 还是满足条件的, 因此, 这有点类似于计算函数与坐标轴围成面积大于0的感觉.

因此如果这个元素大于8, 则会施加一个正影响; 小于等于8, 则会施加一个负影响. 我们将其转换为由1, -1组成的数组, 大于8的时候记为1, 小于等于8记为-1, 有:
[1, 1, -1,-1,-1,-1,1]

所以我们要在这个新数组里, 找到和大于0的最长的子区间(和大于0表示大于8的比不大于8的元素多), 这种子区间和的问题我们考虑前缀和数组(前缀和数组的第i个元素 s u m A r r [ i ] sumArr[i] sumArr[i]表示原数组前 i i i个元素的和). 对1, 1, -1,-1,-1,-1,1计算前缀和, 得到:
[0,1,2,1,0,-1,-2,-1]. 因此, 原数组子区间 [ i , j ] [i,j] [i,j]的和相当于前缀和数组 s u m A r r [ j ] − s u m A r r [ i ] sumArr[j] - sumArr[i] sumArr[j]sumArr[i]的值.

因此问题转化为: 求最大的 j − i j-i ji使得 s u m A r r [ j ] − s u m A r r [ i ] > 0 sumArr[j] - sumArr[i] > 0 sumArr[j]sumArr[i]>0.

因此, 对于前缀和数组的第 j j j个元素 s u m A r r [ j ] sumArr[j] sumArr[j], 我们需要找到离他最远的, 且比他小的 s u m A r r [ i ] sumArr[i] sumArr[i].

这时候我们额外考虑一个问题. 离他最远, 表示 s u m A r r [ i ] < s u m A r r [ j ] sumArr[i] < sumArr[j] sumArr[i]<sumArr[j] s u m A r r [ i − 1 ] ( 如 果 有 的 话 ) > = s u m A r r [ j ] sumArr[i-1](如果有的话) >= sumArr[j] sumArr[i1]()>=sumArr[j], 而对于 i , j i,j i,j中间的数, 我们都不考虑, 因为那不可能是使得这个区间最长的答案. 所以, 我们想维护这样的一个数据结构: 它储存了每一个数组元素 s u m A r r [ j ] sumArr[j] sumArr[j]满足上述条件的可能的答案, 因此, 我们只需要储存sumArr中递减的那些元素即可. 在寻找最长区间时, 我们对sumArr数组从后往前遍历, 对于遍历到的元素 s u m A r r [ j ] sumArr[j] sumArr[j], 我们考察这个数据结构中离他尽可能远的比他小的元素.

因此, 我们维护一个单调栈, 其从栈底到栈顶是递减的, 储存满足严格递减条件的sumArr的下标. 之后从后往前遍历sumArr, 如果栈顶元素小于当前元素 s u m A r r [ j ] sumArr[j] sumArr[j], 则出栈, 早晚碰壁(栈顶元素大于等于当前元素), 这时候假设栈顶元素为 k k k, 则对于当前元素 s u m A r r [ j ] sumArr[j] sumArr[j]来说, 满足前述条件的最优匹配就是 k k k.

额外说一句为什么小于当前元素的都可以出栈, 是因为即使不出栈, 则继续遍历时, 得到的答案也不可能是最优解, 如图所示:

在这里插入图片描述
因此编写代码如下:

class Solution {
public:
    int longestWPI(vector<int>& hours) {
        int result = 0;
        int length = hours.size();

        // 将大于8的元素定义为1, 其余定义为-1
        vector<int> score (length, 0);
        for (int idx = 0; idx < length; ++idx) {
            int sc = hours[idx] > 8 ? 1 : -1;
            score[idx] = sc;
        }

        // 计算前缀和
        vector<int> sumArr (length + 1, 0);
        for (int idx = 1; idx < length + 1; ++idx) 
            sumArr[idx] = sumArr[idx - 1] + score[idx - 1];
    
        /*
        要寻找最大的j - i使得 sumArr[j] - sumArr[i] > 0
        则对于每一个sumArr[k], 只需考察在k之前的最远的比sumArr[k]小的数
        利用单调栈储存
        单调栈从栈底到栈顶是单调递减的 每个元素的意义为原数组中的下标
        */
        stack<int> stk;
        
        for (int idx = 0; idx < length + 1; ++idx) {
            if (stk.empty() || sumArr[stk.top()] > sumArr[idx]) 
                stk.push(idx);
            
        }
        // 倒序遍历sumArr, 寻找当前位置最远的比当前位置小的数的位置 并更新答案
        for (int idx = length; idx > -1; --idx) {
            while(!stk.empty() && sumArr[idx] > sumArr[stk.top()]){
                result = max(result, idx - stk.top());
                stk.pop();
            }
        }

        return result;

    }
};

2. 柱状图中最大的矩形

在这里插入图片描述
这道题与上一题是异曲同工的. 我们考察其中的一个柱状图 h e i g h t s [ i ] heights[i] heights[i], 则我们要考察其左边和右边, 离他最远的比他小的数, 这样才能以他的高度为中心构成一个矩形. 依次枚举 h e i g h t s heights heights, 更新答案即可.

在考察离某个元素最远且比他小的数, 这又可以用到单调栈, 因此这个题构成两个单调栈, 一个表示某元素左边的比他小的最远数的可能位置, 一个表示右边的比他小的最远数的可能位置, 则面积就是两个位置的差再乘以高度.

该题的单调栈也是从栈底到栈顶单调递减(非严格)的.

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n), right(n);  // 储存答案 对于height[i] 最左和
        // 最右的比他小的元素的位置
        
        stack<int> mono_stack;  // 定义栈
        for (int i = 0; i < n; ++i) {
        	// 如果栈顶比当前遍历的值要大于等于 说明这还不是当前元素最远的比它小的
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();
            }
            // 找到了 就加入答案
            left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
            mono_stack.push(i);
        }
		
		// 右侧同理
        mono_stack = stack<int>();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.empty() ? n : mono_stack.top());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
        	// 计算面积
            ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
};


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

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

相关文章

ES6 入门教程 18 Iterator 和 for...of 循环 18.7 for...of 循环

ES6 入门教程 ECMAScript 6 入门 作者&#xff1a;阮一峰 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录ES6 入门教程18 Iterator 和 for...of 循环18.7 for...of 循环18.7.1 数组18.7.2 Set 和 Map 结构18.7.3 计算生成的数据结构18.7.4 类似…

供应叶酸PEG试剂Folic acid-PEG-Azide,FA-PEG-N3,叶酸-聚乙二醇-叠氮

1、名称 英文&#xff1a;Folic acid-PEG-Azide&#xff0c;FA-PEG-N3 中文&#xff1a;叶酸-聚乙二醇-叠氮 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Azide PEG Folic acid&#xff08;FA&#xff09; PEG 4、分子量&#xff1a;可定制&#xff0c;FA-PEG-N3 5…

Web安全之CTF测试赛

Web安全之CTF测试赛 1.000-我是谁 题目描述&#xff1a; 找到诈骗网站bsde.cn的域名注册人及邮箱&#xff0c;将域名注册人的126邮箱填写到下方答案框并点击送出 考察点&#xff1a;whois查询 whois查询网址&#xff1a; https://x.threatbook.com/ //微步在线 http://wh…

ES6 入门教程 17 Promise 对象 17.11 Promise.reject() 17.12 应用 17.13 Promise.try()

ES6 入门教程 ECMAScript 6 入门 作者&#xff1a;阮一峰 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录ES6 入门教程17 Promise 对象17.11 Promise.reject()17.12 应用17.12.1 加载图片17.12.2 Generator 函数与 Promise 的结合17.13 Promise…

ES6 入门教程 15 Proxy 15.2 Proxy 实例的方法 15.2.2 set() 15.2.3 apply()

ES6 入门教程 ECMAScript 6 入门 作者&#xff1a;阮一峰 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录ES6 入门教程15 Proxy15.2 Proxy 实例的方法15.2.2 set()15.2.3 apply()15 Proxy 15.2 Proxy 实例的方法 拦截方法的详细介绍。 15.2.…

【附源码】Python计算机毕业设计天润律师事务所管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

〖全域运营实战白宝书 - 运营角色认知篇③〗- 运营的底层逻辑是什么?

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

【笔试题】【day24】

文章目录第一题&#xff08;完全二叉树的最多结点个数&#xff09;第二题&#xff08;哈夫曼树的带权路径长度&#xff09;第三题&#xff08;堆的重建&#xff09;第四题&#xff08;哈希映射的冲突&#xff09;第一题&#xff08;完全二叉树的最多结点个数&#xff09; 一棵…

UE4 几种蓝图通信的方法

根据视频&#xff08;UE4 几种蓝图通讯的方法&#xff09;所做笔记 目录 方法一&#xff1a;通过公有变量 方法二&#xff1a;通过“获取类的所有actor”节点 方法三&#xff1a;通过蓝图接口 关卡蓝图与蓝图通信 方法一&#xff1a;通过公有变量 步骤&#xff1a; 1.新建…

一款php开发的非常好的OA办公管理系统源码

一款基于TP5 HAdmin Mysql打造的简单实用的开源的企业办公系统框架。可以帮助解决企业办公项目60的重复工作&#xff0c;让开发更多关注业务逻辑。既能快速提高开发效率&#xff0c;帮助公司节省人力成本&#xff0c;同时又不失灵活性。使用本系统可以简单快速地开发出企业级的…

如何将 MATLAB 源代码导出成 Java 的 JAR 包

文章目录编写 MATLAB 源文件安装 Java制作 JAR 包找到 MATLAB 的 JAR 包运行环境&#xff1a; MATLAB R2022a Java 8&#xff08;1.8.0_311&#xff09; Windows 10 教育版 64位 使用混合编程通常都不是好主意&#xff0c;但是有时候会遇到极端的情况。Java 擅长网络编程&am…

vivo霍金实验平台设计与实践-平台产品系列02

vivo 互联网平台产品研发团队 - Bao Dawei 本篇介绍了vivo霍金实验平台的系统架构以及业务发展过程中遇到的问题以及对应的解决方案。 《平台产品》系列文章&#xff1a; 1.vivo平台化实践探索之旅-平台产品系列01 一、前言 互联网企业经历过野蛮生长的开拓红利期之后&#xf…

UE4 TCP通信 (UE客户端与网络调试助手服务端、python服务端通信)

目录 一、使用UE4建立TCP客户端 二、使用网络调试助手建立服务端 三、基于网络调试助手的服务端与UE客户端通信 四、基于python的TCP服务端与UE客户端通信 一、使用UE4建立TCP客户端 1.在虚幻商城中搜索socket来下载TCP Socket Plugin插件 2.安装到引擎&#xff0c;目前…

面向对象分析与设计_类图

判断题 类与对象之间的关系&#xff0c;可以理解为模板与具体实例之间的关系 T 类是现实世界中客观存在的事物或实体。 F 类是具有相同属性和服务的一组对象的集合 T 对象的属性都有值&#xff0c;类的属性没有值 T 类的可见性描述了其属性和操作是否对于其他类可见&…

PHPer 开始使用 Java

出于工作需要&#xff0c;新项目要开始使用 Java 进行开发&#xff0c;注意力就要从 PHP 转移到 Java 上来。个人觉得这是一个挺好的机会&#xff0c;能接触被广泛使用的另一种开发语言和生态。 虽说语言之间存在许多相似之处&#xff0c;但真正落地的过程肯定会存在不少的曲折…

类加载与类文件结构

文章目录类加载机制为什么需要类加载类加载的时机类加载详细过程加载链接初始化类加载器类加载器的分类Java虚拟机自带的类加载器用户自定义类加载器ClassLoader的使用说明双亲委派机制沙箱安全机制类文件的结构类加载机制 为什么需要类加载 首先我们计算机的主要组成是输入设…

ES6 入门教程 15 Proxy 15.2 Proxy 实例的方法 15.2.10 ownKeys() ~ 15.2.12 setPrototypeOf()

ES6 入门教程 ECMAScript 6 入门 作者&#xff1a;阮一峰 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录ES6 入门教程15 Proxy15.2 Proxy 实例的方法15.2.10 ownKeys()15.2.11 preventExtensions()15.2.12 setPrototypeOf()15 Proxy 15.2 Pro…

算法题:SOJ1092: 欧几里得算法

一、BackGroud 在RSA密码体系中,欧几里得算法是加密或解密运算的重要组成部分。它的基本运算过程就是解 x*a1(mod n) 这种方程。 二、The Problem 整个解的过程是这样的&#xff0c;我们用一个例子来说明。 当a&#xff1d;1001 &#xff0c;n&#xff1d;3837时 方程为 x *…

12 个免费 GIS 数据源介绍:最佳全球栅格和矢量数据集

我们生活在当今的信息时代&#xff0c;每天都被大量的信息包围&#xff0c;就免费的 GIS 数据源而言&#xff0c;它的信息似乎是永无止境的。机器学习、人工智能、区块链、预测分析&#xff0c;所有令人惊叹的技术都将革新商业和社会的发展。但如果没有数据的话&#xff0c;这些…

为了摸鱼,我开发了一个工具网站

&#x1f3e1; 博客首页&#xff1a;派 大 星 ⛳️ 欢迎关注 &#x1f433; 点赞 &#x1f392; 收藏 ✏️ 留言 &#x1f3a2; 本文由派大星原创编撰 &#x1f6a7; 系列专栏&#xff1a;《开源专栏》 &#x1f388; 本系列主要输出作者自创的开源项目 &#x1f517; 作品&…