单调栈总结
1、定义
单调栈是一种特殊的栈,其栈内的元素都保持一个单调性(单调递增或者递减)。
- 单调递增栈,从栈底到栈顶依次递增(单调非递减栈:允许有相等)
- 单调递减栈,从栈底到栈顶依次递减(单调非递增栈:允许有相等)
假设下图是一个栈内元素的排列情况(单调递增的栈):
2、作用
利用单调栈,可以找到从左(或者右)遍历第一个比它小(或者大)的元素的位置。
也可以说是 求某组数以其中某一个数字为最小值的最大延伸区间
比如 {2 3 2 5 1 4},以第一个2为最小值可以延伸的区间为{2, 3, 2, 5}
3、算法过程
假设有一个单调递增的栈 S和一组数列: a = { 5 3 7 4}
用数组L[i]
表示 第i个数向左遍历的第一个比它小的元素的位置
如何求L[i]?
3.1 朴素的算法 O(n^2)
可以按顺序枚举每一个数,然后再依此向左遍历。 但是当数列单调递减时,复杂度是严格的O(n^2)。
3.2 单调栈 O(n)
我们按顺序遍历数组(i : 1 -> n),然后构造一个单调递增栈。栈中存放的是元素下标,而非元素本身。
{5 3 7 4}
(1)i = 1时,因为栈为空,L[1] = 0,此时再将第一个元素的位置下标1存入栈中。
此时栈中情况:
(2)i = 2时,因当前元素a[i] = 3小于栈顶元素下标1对应的元素a[1] = 5,故将下标1弹出栈, 此时栈为空 ,故L[2] = 0 。然后将元素3对应的位置下标2存入栈中。
此时栈中情况:
(3)i = 3时,因当前元素a[i] = 7大于栈顶元素下标2对应的元素a[2] = 3,故
L[3] = S.top() = 2 (栈顶元素的值,说明第一个比它小的元素的下标为多少),然后将元素7对应的下标3存入栈 .
此时栈中情况:
(4)i = 4时,因当前元素a[i] =4小于栈顶元素下标3对应的元素a[3] = 7,为保持单调递增的性质,应将栈顶元素下标3弹出 ,而当前元素a[i] =4大于弹出元素后的栈顶元素下标2对应的元素a[2] = 3,不需要再继续弹出, 此时 L[4] = S.top() = 2;然后将元素4对应的下标4存入栈。
此时栈中情况:
(5)至此 算法结束
对应的结果:
a : 5 3 7 4
L : 0 0 2 2
(6)总结
一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素
4、模板
stack<int> s;
for(int i = 1; i <= n; ++i)
{
while(s.size() && a[s.top()] >= a[i]) s.pop();
if(s.empty()) l[i] = 0;
else l[i] = s.top();
s.push(i);
}
5、应用
1.给定一组数,针对每个数,寻找它和它左边第一个比它小的数之间有多少个数。
2.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。
3.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。
6、类型一例题
6、类型一例题
1.POJ 3250
题意:有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。
思路:这也就是应用1所说的求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
题解:POJ 3250题解
7、类型二例题
1.POJ 2559
POJ 2559 &&HDU 1506 && 51NOD 1102 类似
题意:有N个矩形,宽度都为1,给出N个矩形的高度,求由这N个矩形组成的图形包含的最大的矩形面积。
思路:可以转化为求区间最小值乘以区间长度的最大值。普通的思路是两重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
2.POJ 3494
题意:求仅由0,1组成的矩阵中,全部由1组成的小矩阵的最大面积。
思路:这个是上一题POJ 2559的升级版,把一维的操作变成二维的即可。这之前需要一个预处理。
题解:POJ 3494题解
8、类型三例题
1.POJ 2796
题意:给出一个序列,求出一个子序列,使得这个序列中的最小值乘以这个序列的和的值最大。
思路:直接用单调栈解决即可,由于维护单调栈的过程中会改变原数组的值,所以需要加一个sum数组保存前缀和,也方便计算区间元素和。
题解:POJ 2796题解