力扣周赛遇到了单调栈,后来听y总讲思路清楚,但是实现起来自己不太好写,因此总结一下。
什么是单调栈
- 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
- 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大
解决的问题
能够在 O ( n ) O(n) O(n)找到某个位置他左右两侧比它大或者小的第一个数的位置
分析单调递增栈:比如4,3,2,1依次入栈,就是单调递增的。
他可以找出某个位置左右两侧比它大的第一个位置。
怎么找呢?比如[4,3,2,5]序列
从左往右遍历每个元素,4 3 2入栈,接下来判断栈顶和第i个元素的大小关系
a[s.top()]<a[i]
说明5入栈的话会破坏掉该栈的单调递增性质,那么就需要把栈中元素弹出,与此同时栈顶元素也找到了比它大的第一个元素的位置。继续判断也把3 4它的第一个比他们大的位置找到了。这样从左往右遍历就找到了右侧比它大的第一个位置。从右往左遍历就能找到左侧的第一个比它大的位置。
同理单调递减栈也能找到左右两侧比它小的第一个元素的位置
接下来分两种情况考虑:
- 求某个数它左右两侧比它小的第一个位置
- 求某个数它左右两侧比它大的第一个位置
//寻找a[i]左右两侧比他小的数的位置(第一个位置!!)
int a[N],l[N],r[N];
//r[i]表示在a[i]的右边比他小的第一个数的位置(第一个位置!!)
for(int i=1;i<=n;i++){
while(s.size()&&a[s.top()]>a[i]){
r[s.top()]=i;s.pop();
}
s.push(i);
}
//l[i]表示在a[i]的左边第一个比他小的数的位置(第一个位置!!)
for(int i=n;i>=1;i--){
while(s.size()&&a[s.top()]>a[i]){
l[s.top()]=i;s.pop();
}
s.push(i);
}
//寻找a[i]左右两侧比他大的数的位置(第一个位置!!)
//寻找a[i]右两侧比他大的数的位置(第一个位置!!)
for(int i=1;i<=n;i++){
while(s.size()&&a[s.top()]<a[i]){
r[s.top()]=i;s.pop();
}
s.push(i);
}
//寻找a[i]左两侧比他大的数的位置(第一个位置!!)
for(int i=n;i>=1;i--){
while(s.size()&&a[s.top()]<a[i]){
l[s.top()]=i;s.pop();
}
s.push(i);
}
最后需要知道并不是所有的 l [ i ] l[i] l[i]和 r [ i ] r[i] r[i]都能找出来,如果遇到区间问题就需要特判一下边界。例如
84. 柱状图中最大的矩形
思路:这个就是单调栈的应用。对于每个数我们可以找到他左右两侧比他小第一个的位置,那么矩形的面积=(r[i]-l[i]-1)*h[i]。这里就需要特判边界,我们从左往右找他第一个比他小的位置可能找不到,但是给a[n+1]=0,并且遍历到n+1,(默认认为所有元素都比0大),就能给那些找不到的元素赋值r[i]=n+1.便于后续计算。同理从右往左一样
class Solution {
public:
int largestRectangleArea(vector<int>& h) {
int n=h.size();
vector<int>a(n+2),l(n+2),r(n+2);
for(int i=1;i<=n;i++)a[i]=h[i-1];
stack<int>s;
for(int i=1;i<=n+1;i++){
while(s.size()&&a[s.top()]>a[i]){
r[s.top()]=i;s.pop();
}
s.push(i);
}
while(s.size())s.pop();
for(int i=n+1;i>=1;i--){
while(s.size()&&a[s.top()]>a[i]){
l[s.top()]=i;s.pop();
}
s.push(i);
}
int res=0;
for(int i=1;i<=n;i++){
res=max(res,(r[i]-l[i]-1)*a[i]);
}
return res;
}
};
还有一种变形。
1793. 好子数组的最大分数
实际上就是另给你一个k,要求k在这个区间。
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
int n=nums.size();
vector<int>l(n+2),r(n+2),a(n+2);
k++;
for(int i=1;i<=n;i++)a[i]=nums[i-1];
stack<int>st;
for(int i=1;i<=n+1;i++){
while(st.size()&&a[st.top()]>a[i]){
r[st.top()]=i;st.pop();
}
st.push(i);
}
while(st.size())st.pop();
for(int i=n;i>=0;i--){
while(st.size()&&a[st.top()]>a[i]){
l[st.top()]=i;st.pop();
}
st.push(i);
}
int res=0;
for(int i=1;i<=n;i++){
if(k>l[i]&&k<r[i])
res=max(res,(r[i]-l[i]-1)*a[i]);
}
// for(int i=1;i<=n;i++){
// cout<<l[i]<<' ';
// }
// cout<<'\n';
// for(int i=1;i<=n;i++){
// cout<<r[i]<<' ';
// }
// cout<<'\n';
return res;
}
};
2021-3-27更新一道
B 找山坡
题意:
找到两个坐标,这两个坐标的值相等,并且他们之间的值都大于等于这两个坐标上的值. 这两个坐标相减最大能是多少.
思路:实际上就是找第i个数右边第一个比它小的数。不过此时要注意中间相等的数不能被放到栈里面比如[1,2,3,2,2,1]。也就是说2以后再出现跟2相等的数,我们只需要统计答案,而不需要压栈,不然会影响后面的2和栈中第一个2做差的结果。
#include <bits/stdc++.h>
#define pb push_back//vector,deque
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=1e6+5;
int a[N];
int main() {
// solve();
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
stack<int>s;
int res=0;
for(int i=1;i<=n;i++){
while(s.size()&&a[s.top()]>a[i]){
s.pop();
}
if(s.size()&&a[i]==a[s.top()]){
res=max(res,i-s.top());continue;
}
s.push(i);
}
cout<<res<<'\n';
return 0;
}