单调栈的性质
单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性。
假设下图是一个栈内元素的排列情况(单调递增的栈):
此时插入情况有两种:
(1)插入元素大于栈顶元素:
因为7 > 6,满足栈内元素单调递增的性质,所以可以直接插入7到栈顶
(2)当插入元素小于栈顶元素的时候,当插入3的时候,需要将栈顶的6,4弹出,在插入,例如:
功能
利用单调栈可以找出从左/右遍历第一个比它小/大的元素的位置
例如说:
现在有一个数组a[4] = {5,7,3,4},要你找出每一个元素左边最靠近它的最小元素的位置(下标+1),如果没有元素比它小,则输出0
此时我们会想到,从每一个数字开始想左边遍历,这是一个朴素的算法,我们就不再多说了。
我们要说的是怎么样利用单调栈来解决该问题
1.设计一个数组res[4],来保存结果
2.开一个栈,这个栈的性质是,当栈为空的时候,将对应的res[i] = 0;当栈顶元素是从左开始,输入的数中最小的那个的下标,如果进栈的元素小于或者等于a[栈顶元素],则输出0;如果大于,则输出栈顶元素+1。
下面上代码:
#include<iostream>
#include<stack>
using namespace std;
int main() {
//n表示要找的总数
int n; cin >> n;
//a用来保存输入的数据
int* a = new int[n];
//res_left用来保存对应的结果
int* res_left = new int[n];
stack<int>s;
for (int i = 0; i < n; i++)cin >> a[i];
//找出a[i]左边和右边最近的一个比他小的值的下标
for (int i = 0; i < n; i++) {
while (!s.empty() && a[i] <= a[s.top()])s.pop();
if (s.empty())res_left[i] = 0;
else res_left[i] = s.top() + 1;
s.push(i);
}
//这个栈就是递减的一个,取最小值的时候,复杂度是O(1)
for (int i = 0; i < n; i++) {
cout << res_left[i] << " ";
}
}
输入数据: 6
4 7 8 5 3 1
输出数据:
0 1 2 1 0 0
当然这是一个简单的小应用
例题1:
题目背景:
高个子同学可以看到身高比自己矮的同学,但是目光一旦遇到高于或等于自己的同学后,便无法继续向前看。先给出所有同学的身高,求所有同学能看到的同学数量之和。
可能不和逻辑…
输入格式:
第一行一个n,表示总共有多少名同学
接下来输入n个数字,表示从后到前的同学的身高
输出格式
一个数字,同学们能看到的同学的总数
解题思路:
1.朴素的算法:先读入数据,然后从最后一名开始向前遍历,遇到小于自己身高的就加1,遇到大于等于自己身高的就停止,时间复杂度应该是O(n*logn)
2.利用单调栈:每读入一个数据,就判断前面有多少名同学能看到他,个子高的能看到个子矮的,所以这应该是递减的栈,时间复杂度是O(n)
代码实现:
#include<iostream>
#include<stack>
using namespace std;
int main() {
int n; cin >> n;
int sum = 0;
//开一个栈,单调递减,对于栈顶元素而言,能看到他的就是后面元素的个数
stack<int>s;
for (int i = 0; i < n; i++) {
int temp;
cin >> temp;
//当输入的人较高时,将较矮的人剔除栈
while (!s.empty() && temp >= s.top())s.pop();
sum += s.size();
s.push(temp);
}
cout << sum << endl;
return 0;
}
输入数据:6
188 169 180 172 190 165
输出数据:
5
例题2:
输入格式:
一个整数n,表示总共有n个宽度为1的矩形块
n个整数,表示每一个矩形块的高度,按照输入的顺序,排列相应高度的矩形块
输出格式:
一个整数,表示这些矩形块能够构成的最大矩形块。
仔细观察可知,这个矩形一定以某一个输入的矩阵高度为高度
解题思路:
1:常规思路,将这些高度保存起来,用一个数组;然后对数组进行遍历,对每一个数字进行的操作是,向左右找,直到碰到高度小于自己高度或者是到边界的时候,算一下总共的矩阵块数,再乘以高度。这样每一个输入的矩阵都对应一个以他为高度形成的最大矩阵,然后取最大值就行。但是这个算法的最坏情况是O(N*2)显然是一个很大的数据,这个属于朴素算法
2.利用单调栈:这里就不多bb,直接上代码,代码上面有注释
#include<iostream>
#include<stack>
using namespace std;
//这个节点用来保存该矩阵的高度,能触及的最边缘的下标
class Node {
public:
int val, left, right;
};
//这是一个根据矩阵高度排列的递增栈
stack<Node>s;
//为什么要设计一个递增的栈:因为遇到高度小的就需要停止扩展边界
int main() {
int n; cin >> n;
Node* node = new Node[n + 5];
//初始化
for (int i = 0; i < n; i++) {
cin >> node[i].val;
node[i].left = node[i].right = i;
}
long long int area = 0;
//为了当栈内还剩余元素的时候,不需要单开逻辑判断
//简单的说就是为了清空栈内元素
node[n + 1].val = -1;
node[n + 1].left = node[n + 1].right = n + 1;
for (int i = 0; i < n + 1; i++) {
//由于这是一个递增的栈,当要压入的元素小于栈顶元素的时候
while (!s.empty() && node[i].val <= s.top().val){
Node newnode = s.top();
s.pop();
//说明将要压入的矩阵可以向左边拓展
node[i].left = newnode.left;
//如果删除栈顶元素之后还有元素,那么栈顶元素可以向右拓展
if (!s.empty()) {
s.top().right = newnode.right;
}
//弹出每一个矩阵的时候,计算该矩阵能形成的最大矩阵的面积
long long int val = ((long long)newnode.right - newnode.left + 1) * (long long)newnode.val;
if (val > area)area = val;
}
s.push(node[i]);
}
cout << area << endl;
}
输入数据:6
2 1 5 6 2 3
输出数据:
10