【数据结构】单调栈专题
模板题:830. 单调栈
关键要理解的地方是什么时候就弹出栈顶。本题找的是距离x最近的比x小的数,所以栈里的数如果大于等于x那么就一定不会被用到(因为x比栈顶元素更优),所以可以全部删掉(弹出),最后剩下的栈顶元素就是答案。
STL版本
#include <iostream>
#include <stack>
using namespace std;
stack<int> stk;
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
{
int x;
scanf("%d", &x);
while (stk.size() && stk.top() >= x) stk.pop();
if (stk.size()) printf("%d ", stk.top());
else printf("-1 ");
stk.push(x);
}
return 0;
}
作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1161663/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
数组模拟栈
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int stk[N], tt;
int main()
{
int n;
scanf("%d", &n);
while(n -- )
{
int x;
scanf("%d", &x);
while(tt && stk[tt] >= x) tt -- ;
if(tt) printf("%d ", stk[tt]);
else printf("-1 ");
stk[++ tt] = x;
}
return 0;
}
131. 直方图中最大的矩形
本题要利用单调栈来优化时间复杂度。
要找到左边(右边同理)第一个比当前矩形矮的矩形,所以当栈顶元素的高度大于等于当前高度h[i]
,那么栈顶元素就一定不会被用到(因为当前高度更优),可以删掉(弹出),重复此操作直到栈顶元素的高度小于当前高度或栈空(无解)为止
小细节:让h[1]
与h[n + 1] = 1
,确保所有矩形两边都有比其矮的矩形,可以方便处理边界问题。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n;
int h[N], l[N], r[N], q[N];
int main()
{
while(scanf("%d", &n), n)
{
for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
h[0] = h[n + 1] = -1;
int tt = 0;
q[0] = 0;
for(int i = 1; i <= n; i ++ )
{
while(h[i] <= h[q[tt]]) tt -- ;
l[i] = q[tt];
q[++ tt] = i;
}
tt = 0;
q[0] = n + 1;
for(int i = n; i; i -- )
{
while(h[i] <= h[q[tt]]) tt -- ;
r[i] = q[tt];
q[++ tt] = i;
}
LL res = 0;
for(int i = 1; i <= n; i ++ )
res = max(res, (LL)h[i] * (r[i] - l[i] - 1));
printf("%lld\n", res);
}
return 0;
}
作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3210396/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
152. 城市游戏
和直方图中的最大矩形基本一样,读入的时候需要稍微处理一下.
s[i][j]
表示坐标为(i, j)
的格子上方有多少个连续的F(相当于构造直方图)
递推关系:一个格子上方的连续F数=其上一个格子s[i - 1][j]
的连续F数+1
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int n, m;
int s[N][N], l[N], r[N], q[N];
int work(int h[])
{
int tt = 0;
h[0] = h[m + 1] = -1;
q[0] = 0;
for(int i = 1; i <= m; i ++ )
{
while(h[q[tt]] >= h[i]) tt -- ;
l[i] = q[tt];
q[++ tt] = i;
}
tt = 0;
q[0] = m + 1;
for(int i = m; i; i -- )
{
while(h[q[tt]] >= h[i]) tt -- ;
r[i] = q[tt];
q[++ tt] = i;
}
int res = 0;
for(int i = 1; i <= m; i ++ )
res = max(res, h[i] * (r[i] - l[i] - 1));
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
{
char c;
cin >> c;
if(c == 'F') s[i][j] = s[i - 1][j] + 1;
}
int res = 0;
for(int i = 1; i <= n; i ++ ) res = max(res, work(s[i]));
printf("%d\n", res * 3);
return 0;
}
作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3216854/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1574. 接雨水
本题单调栈找的是一个矩形两边第一个比其高的矩形,当栈顶元素小于等于当前元素,说明当前元素是更优解,即栈顶元素永远不会作为答案,所以可以删掉(出栈),然后将当前元素入栈,然后在维护单调栈的过程中顺便把雨水面积求出。
雨水面积是一块一块地算,当当前元素高度大于等于栈顶,先把栈顶元素与当前元素之间的雨水算一下,然后栈顶元素出栈。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n;
int h[N], q[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
int tt = -1;
int res = 0;
for(int i = 1; i <= n; i ++ )
{
int last = 0;
while(tt >= 0 && h[q[tt]] <= h[i])
{
res += (h[q[tt]] - last) * (i - q[tt] - 1);
last = h[q[tt]];
tt -- ;
}
if(tt >= 0) res += (h[i] - last) * (i - q[tt] - 1);
q[++ tt] = i;
}
printf("%d\n", res);
return 0;
}
作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3218137/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
600. 仰视奶牛
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n;
int h[N];
int stk[N], tt;
int s[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
for(int i = 1; i <= n; i ++ )
{
while(tt && h[stk[tt]] < h[i])
{
s[stk[tt]] = i;
tt -- ;
}
stk[++ tt] = i;
}
for(int i = 1; i <= n; i ++ ) printf("%d\n", s[i]);
return 0;
}
1413. 矩形牛棚
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 3010;
int n, m, p;
int g[N][N], h[N][N], stk[N];
int l[N], r[N];
int work(int h[])
{
int tt = 0;
for(int i = 1; i <= m; i ++ )
{
while(tt && h[stk[tt]] >= h[i]) tt -- ;
if(!tt) l[i] = 0;
else l[i] = stk[tt];
stk[++ tt] = i;
}
tt = 0;
for(int i = m; i; i -- )
{
while(tt && h[stk[tt]] >= h[i]) tt -- ;
if(!tt) r[i] = m + 1;
else r[i] = stk[tt];
stk[++ tt] = i;
}
int res = 0;
for(int i = 1; i <= m; i ++ )
res = max(res, h[i] * (r[i] - l[i] - 1));
return res;
}
int main()
{
scanf("%d%d%d", &n, &m, &p);
while(p -- )
{
int x, y;
scanf("%d%d", &x, &y);
g[x][y] = 1;
}
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
if(!g[i][j])
h[i][j] = h[i - 1][j] + 1;
int res = 0;
for(int i = 1; i <= n; i ++ ) res = max(res, work(h[i]));
printf("%d\n", res);
return 0;
}
作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3276445/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
41. 包含min函数的栈
开一个辅助栈stk_min,用于处理get_min()
函数
每次加入新元素时,原栈stk正常加入,辅助栈加入栈顶元素与新元素中较小的那个元素。
这样辅助栈存的就是前i个加入的数中的最小值,每次询问时只需输出栈顶即可。
每次弹栈时,辅助栈和原栈操作相同。
class MinStack {
public:
/** initialize your data structure here. */
stack<int> stk, stk_min;
MinStack() {
}
void push(int x) {
stk.push(x);
if(stk_min.size()) x = min(x, stk_min.top());
stk_min.push(x);
}
void pop() {
stk.pop();
stk_min.pop();
}
int top() {
return stk.top();
}
int getMin() {
return stk_min.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
写法二:维护一个单调递减的栈
class MinStack {
public:
/** initialize your data structure here. */
stack<int> stk, stk1;
MinStack() {
}
void push(int x) {
stk.push(x);
if(stk1.empty() || stk1.top() >= x) stk1.push(x);
}
void pop() {
if(stk1.top() == stk.top()) stk1.pop();
stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return stk1.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
LeetCode 456. 132模式
下图来自leetcode评论区“宫水三叶”
自己的话:
k代表的是第二大的元素,用出栈的元素更新k(有元素出栈就代表有更大的元素要入栈,所以k是第二大的元素),栈里的始终是最大的元素。所以当有nums[i] < k
就认为找到了一组解。
class Solution {
public:
bool find132pattern(vector<int>& nums) {
int k = INT_MIN;
stack<int> stk;
for(int i = nums.size() - 1; i >= 0; i -- )
{
if(nums[i] < k) return true;
while(stk.size() && stk.top() < nums[i])
{
k = max(k, stk.top());
stk.pop();
}
stk.push(nums[i]);
}
return false;
}
};
1575. 盛水最多的容器
本题的传统方法是单调栈,但是本题有一个巧解是双指针。
i,j
两个指针,先计算面积
然后每次比较i,j
,小的那个向中间移动一位(若相等则谁移动都可以),然后计算面积,在所有面积中取最大值即可
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n;
int h[N];
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++ ) scanf("%d", &h[i]);
int res = 0;
for(int i = 0, j = n - 1; i < j;)
{
res = max(res, min(h[i], h[j]) * (j - i));
if(h[i] < h[j]) i ++ ;
else j -- ;
}
printf("%d\n", res);
return 0;
}
作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3214975/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。