A-最大矩形(单调栈)
一、题目描述
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
Input
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数。
你可以假定1 <= n <= 100000.
然后接下来n个整数h1, ..., hn, 满足 0 <= hi <= 1000000000.
这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
Output
对于每组测试数据输出一行一个整数表示答案。
Sample Input
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
Sample Output
8
4000
二、思路与算法
首先,本题最核心的算法为单调栈的实现,通过两次单调栈,求得每个元素左边第一个小于它的元素编号、每个元素右边第一个小于它的元素编号。
用Left数组存储每个元素左边第一个小于它的元素编号。
用Right数组存储每个元素右边第一个小于它的元素编号。
接下来通过两次遍历,一次从0到n-1,一次从n-1到0,分别填充Right和Left。
以填充Right为例:
选用单调递增栈(s数组实现),当栈非空且大于等于栈顶元素时,入栈,否则弹出栈顶元素直到栈空或栈顶元素小于该元素。
弹出时注意,把元素A弹出去的元素B,B就是A右边第一个小于A的元素,以此规律,每次弹出元素的同时,把Right中该元素对应位置填充好。
最后,清空栈中元素。
如果所有元素都遍历结束,栈中还有元素,则这些元素右侧没有比它小的元素,所以把它们对应的Right位置填充为直方图末尾。
Left数组填充与Right数组填充非常相似。
最后,遍历直方图中每个高度,计算以某高度为矩形的高,最大宽度(Right对应值、Left对应值计算得到),得到的矩形面积,从所有高度对应矩形面积中选出最大的一个,即为结果。
三、代码实现
#include<cstdio>
using namespace std;
int n=0;
int a[100010]={0};
int s[100010]={0};//单调栈
int Right[100010]={0};//存每个元素右侧第一个小于它的元素标号
int Left[100010]={0};//存每个元素左侧第一个小于它的元素标号
int main()
{
scanf("%d",&n);
while(n!=0)
{
int p=-1;
long long maxSize=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
for(int i=0;i<n;i++){
while(p>=0&&a[i]<a[s[p]]){
Right[s[p]]=i;//把某元素挤出栈的元素,就是它右边第一个比它小的元素
p--;//出栈
}
s[++p]=i;//入栈
}
while(p>=0) //清空栈
{ Right[s[p]]=n;
p--;
}
p=-1;
for(int i=n-1;i>=0;i--){
while(p>=0&&a[i]<a[s[p]]){
Left[s[p]]=i; p--;
}
p++;
s[p]=i;
}
while(p>=0)
{Left[s[p]]=-1; p--;}
for (int i=0;i<n;i++){
if(maxSize<(long long)a[i]*(Right[i]-Left[i]-1)){
maxSize=(long long)a[i]*(Right[i]-Left[i]-1);
}
}
printf("%d\n",maxSize);
scanf("%d",&n);
}
return 0;
}
四、经验与总结
-
注意!一直WA在于最大面积的计算方法处:
maxSize=(long long)a[i]* (Right[i]-Left[i]-1);
一定要先把a[i]转换为long long形式,然后再做乘法运算,运算中发生隐式转换,结果就是long long类型的,才不会出错!
如果long long temp;
temp=(long long)(a[i]*(Right[i]-Left[i]-1));
则依然会出错,精度不够,造成WA!
所以,切记运算如果可能超出精度时,一定要先对某个操作数进行形式转换!否则运算时精度已经损失掉了,再转换形式已经没用了。
- 可以更多地把数组等变量声明为全局变量,通过对题目中数据规模的掌握。
- 此题填充Right、Left数组时最容易忽略掉遍历完成后,清空栈的操作,需要注意。