[杂记]算法: 单调栈
创始人
2024-02-04 03:53:35

0. 引言

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

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

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

stack 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个元素sumArr[i]sumArr[i]sumArr[i]表示原数组前iii个元素的和). 对1, 1, -1,-1,-1,-1,1计算前缀和, 得到:
[0,1,2,1,0,-1,-2,-1]. 因此, 原数组子区间[i,j][i,j][i,j]的和相当于前缀和数组sumArr[j]−sumArr[i]sumArr[j] - sumArr[i]sumArr[j]−sumArr[i]的值.

因此问题转化为: 求最大的j−ij-ij−i使得sumArr[j]−sumArr[i]>0sumArr[j] - sumArr[i] > 0sumArr[j]−sumArr[i]>0.

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

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

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

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

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

class Solution {
public:int longestWPI(vector& hours) {int result = 0;int length = hours.size();// 将大于8的元素定义为1, 其余定义为-1vector score (length, 0);for (int idx = 0; idx < length; ++idx) {int sc = hours[idx] > 8 ? 1 : -1;score[idx] = sc;}// 计算前缀和vector 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 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. 柱状图中最大的矩形

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

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

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

class Solution {
public:int largestRectangleArea(vector& heights) {int n = heights.size();vector left(n), right(n);  // 储存答案 对于height[i] 最左和// 最右的比他小的元素的位置stack 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();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;}
};

上一篇:关于合作的成语

下一篇:DataObjectImpl

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...