单调栈是栈中的元素从栈顶到栈底单调递增或递减。单调栈只能从栈顶部添加或者删除元素,以单调递增栈为例,元素从栈顶到栈底逐渐递增,假设当前元素为e,在入栈时,从栈顶往下找,如果栈顶元素小于当前元素e,则弹出栈顶元素,直到栈顶元素大于等于该元素e,然后将e入栈;出栈时,直接弹出栈顶元素,即可得到栈中当前所有元素的最小值。
由于每个元素都会入栈和出栈一次,因此平摊下来,时间复杂度为O(n).
单调栈示例: poj 2559 Largest Rectangle in a Histogram
题目要求求出由给定的整数数组确定的直方图中,连续的一块最大的矩形面积,其中每个直方列的宽度均为1。
题目解法:
将关注点放在直方图的每个列上,由该列的高度确定的一个矩形(从该点的峰点出发,分别向左和向右延伸,得到最大的宽度,从而得到由该高度确定的矩形的面积)。然后进行一次遍历,得到所有点确定的矩形中最大的一个。
从一点向左(或右)延伸的最大长度(以向左延伸为例),就是从该点出发向左走,如果左边点的高度大于等于该点,则长度加1, 直到遇到的点的高度小于该点的高度则停止,得到最大值。如果使用纯粹的搜索方法,对每个点都做一遍“从该点开始,向左走直到遇到小于它高度的点” 则,复杂度为 O(n^2),仔细分析可以发现: 如果 进行完 Ai的搜索,得到高度小于Ai的点为P,在进行Ai+1的 搜索的时候,如果 h[i+1] > h[i], 则直接得到结果i;如果h[i+1] < h[i],则 从P到i之间的点的高度 肯定大于h[i+1](因为它们大于h[i]),所以只需要从P开始接着往前找即可。 这样的搜索过程,可以简化为在一个单调递减的栈中进行查找,这个过程可以用单调栈来实现。 在实现的时候,考虑求该点向左延伸时,左边最小的点的索引,并且将索引用栈来存储。
代码实现:注意 long long 的类型,以及 long long result = (right[i] - left[i] + 1)*hist[i]; (在hist 为整数数组时候,需要强制转换为 long long)
不使用单调栈!
#include<stdio.h> #include<iostream> #include<string> #include<string.h> #include<vector> #include<stack> #include<queue> #include<deque> #include<list> #include<set> #include<set> #include<map> #include<functional> #include<algorithm> using namespace std; const int kMax = 100005; /* 庞大的数组,从小处入手 =_+, 考虑从直方图中每个位置向左和向右扩展,可以得到的以当前位置处高度为最低高度的序列长度w。 这样,可以求出从位置i,分别向左向右扩展,保持矩形高度为height[i],得到的矩形面积 为height[i]*w 最后遍历 i,求出最大值。 对于每个位置i,我们想要知道它最左边的高度比它的高度低的位置,以及最右边的高度比它的高度低的位置。 最naive的方法是对每个i都向左和向右扫描,找到第一个比height[i] 小的位置j,k。这种方法中会有很多重复计算: 假设 位置 i > j > k 现在考虑 k 向左扩展能够得到的最大长度,此时向左扫描到了j,如果 height[j] < height[k],此时扫描可以结束;如果 height[j] >= height[k] 继续向左扫描,而之前已经计算过了j向左扩展得到的最大长度,假设j左边的位置 i处 height[i] >= height[j], 那么height[i] >= height[k], 在从j向左扫描的时候,路过了i,现在从k向左扫描,还要再路过i一次!!!! 出现了重复。 那么,如何消除呢? 在j向左扫描的时候,获得了j左边第一个height[p]比height[j]小的位置p,那么再从k向左扫描的时候,从j,可以直接跳到p....核心想法就是将每个位置 的左边第一个比该位置处高度小的那个点记录下来,之后直接跳到那个点。 */ //height 数组 int height[kMax]; // left_less_index[i] 表示height数组中从i向左查找,height 第一个比 height[i] 小的位置 int left_less_index[kMax]; // right_less_index[i] 表示height数组中从i向右查找,height 第一个比 height[i] 小的位置 int right_less_index[kMax]; long long int solve(int n){ int j; for (int i = 0; i < n; i++){ j = i - 1; while (j >= 0 && height[j] >= height[i]){ j = left_less_index[j]; } left_less_index[i] = j; } for (int i = n - 1; i >= 0; i--){ j = i + 1; while (j < n && height[j] >= height[i]){ j = right_less_index[j]; } right_less_index[i] = j; } long long result = 0; for (int i = 0; i < n; i++){ long long tmp = ((long long)height[i]) * (right_less_index[i] - left_less_index[i] - 1); result = max(tmp, result); } return result; } int main(){ int n; while (scanf("%d", &n)){ if (n == 0) break; for (int i = 0; i < n; i++){ scanf("%d", &height[i]); } long long int result = solve(n); printf("%lld ", result); } return 0; }
使用单调栈!
/* 除了用数组 left_less_index, right_less_index 记录下来每个位置i处左边/右边第一个高度比height[i]小的位置之外,还可以使用单调栈。 栈中存放的是 height数组中的索引【注意,单调栈和单调队列中存放的都是数组元素的索引】。每次计算完位置i,栈顶元素就更新为 i。 每次计算的时候,都从栈顶向栈底寻找第一个height[index] 比height[i]小的index, 而将中间过程中的那些不满足条件的index都弹出栈。 */ //height 数组 int height[kMax]; //单调栈,栈中存放height数组的索引,从栈顶到栈底 栈中元素(索引)所在位置的height单调递减 int mon_stack[kMax]; //left_width[i] 表示i处的元素可以向左扩展的最大长度,不包括i int left_width[kMax]; //right_width[i] 表示i处的元素可以向右扩展的最大长度,不包括i int right_width[kMax]; long long int solve(int n){ int top = -1; for (int i = 0; i < n; i++){ while (top >= 0 && height[mon_stack[top]] >= height[i]){ top--; } left_width[i] = top < 0? i: i - mon_stack[top] - 1; mon_stack[++top] = i; } top = -1; for (int i = n - 1; i >= 0; i--){ while (top >= 0 && height[mon_stack[top]] >= height[i]){ top--; } right_width[i] = top < 0? n - i - 1: mon_stack[top] - i - 1; mon_stack[++top] = i; } long long result = 0; for (int i = 0; i < n; i++){ long long tmp = ((long long)height[i]) * (left_width[i] + right_width[i] + 1); result = max(tmp, result); } return result; } int main(){ int n; while (scanf("%d", &n)){ if (n == 0) break; for (int i = 0; i < n; i++){ scanf("%d", &height[i]); } long long int result = solve(n); printf("%lld ", result); } return 0; }