单调栈

  单调栈是栈中的元素从栈顶到栈底单调递增或递减。单调栈只能从栈顶部添加或者删除元素,以单调递增栈为例,元素从栈顶到栈底逐渐递增,假设当前元素为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\n", 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\n", result);
	}
	return 0;
}

 

posted @ 2015-06-03 21:15  农民伯伯-Coding  阅读(618)  评论(0编辑  收藏  举报