单调栈的作用是:用 的时间得知所有位置两边第一个比他大(或小)的数的位置。
递增(减)栈
应用场景:
针对某个数,寻找它和它【左/右】两边第一个比他【大/小】的数,以及相距多少距离。 1.> 接雨水 针对某个洼地,当前能接的雨水等于左右两侧【最大高度】的最小值-当前洼地的高度。(> )
对于某个洼地来说,其能盛水的量由【左右边界确定】,对于洼地> > ,显然1的左边届由0界定,将其加入栈中,待确定其右边界,当有 显然1的右边界由2界定,弹出1,其为最低高度,并得到当前栈顶0故0-1-2之间可以存水【】 【stack.top()↓> 】 2.柱形图中最大的矩形 针对某个柱子,其能形成的最大矩形由其左右两侧【最近的小于其高度】的柱子决定(> ) 1.针对某个柱子p,在其> (> 从前向后遍历> )> 之前> 有> > ,则其必然不被所界定左边界(因为比较小,离p更近,)。当遍历到柱子p时,若> 栈顶元素>p(说明p之后的柱子由p来界定左边界,栈中比p高的柱子都没机会了,此时也说明当前st.top元素的右边界为p(离的最近且小)),应弹出> ,直到> 栈顶元素<p> ,则该栈顶元素为柱子p的左边界。> 【stack.top()>cur,弹出,故使用递增栈> ↑> 】 2.同理右边界 3.算出每个柱子所能得到的最大矩形即可 3.牛向右看能看多少只其他牛。 针对某个牛,其能看到的牛的个数由其右侧【最近的高于其高度的牛】所决定(> > ) 1.针对某个牛p,假设> (从后向前遍历)> 其后> 有> > ,其右边界必不为所界定(因为且又高)。当遍历到牛p时,若栈顶元素
p,则该栈顶元素为p的右边界。> 【> stack.top()<cur,确定右边界,弹出,故使用递减栈↓】**
1.定义
从栈底元素到栈顶元素呈单调递增或单调递减,栈内序列满足单调性的栈;
2.原理
当新元素在单调性上优于栈顶时(单增栈新元素比栈顶大,单减栈新元素比栈顶小),压栈,栈深+1;
- 当新元素在单调性与栈顶相同(新元素于栈顶相同)或劣于栈顶时(单增栈新元素比栈顶小,单减栈新元素比栈顶大),弹栈,栈深-1;
3.应用
1.求最长的单调上升、递减区间
eg.Loongint的花篮
如果对于区间Si,Sj中任意的花篮都比Si高且比Sj低,那么这个区间称为一个美学区间。 如果根本不存在美学区间,输出-1。 如果存在美学区间,那么如果任意区间的长度都小于等于k,那么输出最大的长度,否则输出最大长度比k大多少(MaxLength-k)。
2.针对每个数,寻找它和它左 / 右边第一个比它大 / 小的数的值,以及相距多少个数
有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。
解析:
对于第i
头牛,假设其后续能观测到的牛的标号分别为[i+1,i+2,.....,s]
,且height[i+1,....,s]<height[i]
,则其观测到牛的总数为s-i+2
;
归纳一下该问题解法:
- 枚举某一头牛
i
,其高度为height[i]
- 随后向后扩展,使得右侧所有牛的高度均<
height[i]
。换句话说,即找到右侧最近的高度>height[i]的牛的位置;对于两头牛,则任何在之前的牛,一定不会是右侧最近的高度>height[i]的牛的位置;
由于寻找右侧最近较高的位置,故采取递增栈,存取数据为【当前牛所能看到的牛的数量】
对于牛j0
,若当前栈顶>牛,则表示当前栈顶即为其右侧最近较高的位置
3.接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)
思想:递减栈
- 设高度列表为[],遍历此列表,设索引值i
- 初始化:单调减栈stack
- 遍历:记每个节点为
- 判断:若栈不空且栈顶元素<,则。
- 取出洼地:得当前
stack.top()
的值,记为j
,并出栈。 - 雨水长度:出栈后若栈仍不为空,说明存在左边界,记录为
stack.top()
,则否则 - 雨量高度:
- 将当前雨水高度存入
ans
- 取出洼地:得当前
- 入栈:将柱子位置i入栈
- 判断:若栈不空且栈顶元素<,则。
- 遍历完成时返回
ans
实现
int trap(vector<int>& height) {
stack<int> s;
int n=height.size(),ans=0;
for(int i=0;i<n;i++){
while(!s.empty() && height[i]>height[s.top()]){
int idx=s.top();
s.pop();
int dis=s.empty()?0:(i-s.top()-1);
int bound_height=dis==0?0:(min(height[i],height[s.top()])-height[idx]);
ans+=dis*bound_height;
}
s.push(i);
}
return ans;
}
4.多个区间中的最值 / 某个数为最值的最长区间
eg.POJ 2796
给你一段区间,需要你求出(在这段区间之类的最小值*这段区间所有元素之和)的最大值
5.柱状图中最大的矩形:
求直方图中的最大矩阵 For example, Given height =
[2,1,5,6,2,3]
, return10
.
2.POJ 3494(题1的二维版本)
求仅由0,1组成的矩阵中,全部由1组成的小矩阵的最大面积。
思路
关键在于查找height[i]的左右边界,满足当前height[i]对应的最大面积为(r-l-1)*height[i];
- 单调栈前一个元素为左边界
- 单调栈出栈时,说明其后出现了比栈顶更小的元素,故该元素为栈顶元素的右边界
-
算法
当栈不为空且
Height[current]<Height[st.top()]:
获取
top
,出栈;- 判断当前栈是否为空
- 非空则
r=current,l=st.top()
- 空则
r=current,l=-1
- 非空则
- 则当前面积为
(r-l-1)*height[i]
int largestRectangleArea(vector<int>& heights) {
stack<int> st;
int n=heights.size(),ans=0;
heights.push_back(-1);
for(int i=0;i<n+1;i++){
while(!st.empty() && heights[st.top()]>heights[i]){
int idx=st.top();
st.pop();
int l=st.empty()?-1:st.top();
ans=max(ans,(i-l-1)*heights[idx]);
}
st.push(i);
}
return ans;
}
6.★★子数组的最小值之和
给定一个整数数组
A
,找到min(B)
的总和,其中B
的范围为A
的每个(连续)子数组。 由于答案可能很大,因此返回答案模10^9 + 7
。
思路
关键在于寻找A[i]所属的区间,满足A[i]为该区间内的最小值
- 单调栈的前一个元素为该元素的左边界
单调栈出栈时,说明其后出现了比栈顶更小的元素,该元素为栈顶元素的右边界。
算法
当栈不为空且
A[current]<=A[st.top()]
时:获取
top
,并出栈- 求取
current
左侧连续n个大于A[current]
n=top-st.top
- 求取右侧连续m个大于A[current]
m=current-top
- 求得连续子数组数量为m+n+mn+1=(m+1)*(n+1)
ans+=(m+1)*(n+1)
current++;
实现
int sumSubarrayMins(vector<int>& A) {
stack<int> inc_stack; //单调递增栈, 存储的是数组A的索引
A.push_back(-1); //保证栈里所有元素都弹出
int len = A.size();
// {__n__ A[i] ___m____ }; 如 8, 4, 6, 5, 7, 9, 3, 0 -> 对于A[i] = 5, n = 1 ([6]), m = 2 ([7, 9])
// A[i]左侧有n个连续的大于A[i]的数, 右侧有连续m个大于A[i]的数, 则A[i]作为最小值的数组个数为(m+1)*(n+1)
// 关键即为找到每个A[i]对应的区间, 满足A[i]为该区间内的最小值
int ans = 0;
for(int i = 0; i < len; i++){
while(!inc_stack.empty() && A[inc_stack.top()] >= A[i]){ // A[i]比栈顶元素小, 说明栈顶元素的区间截止, 该处理栈顶元素
int index = inc_stack.top(); //取栈顶索引
inc_stack.pop();
int pre = index - (inc_stack.empty()? -1 : inc_stack.top()); //对应n+1
int after = i - index; //对应m+1
ans += A[index]*pre*after; //A[i]作为最小值的次数为(m+1)*(n+1), 即ans加上A[i]*(m+1)*(n+1)
}
inc_stack.push(i);