题目描述:
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
输入格式:
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
Sample Input:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出格式:
对于每组测试数据输出一行一个整数表示答案。
Sample Output:
8
4000
思路:
要求取最大面积,重点是直方图每个高度的矩形可向左向右可延展的最大宽度。
暴力解法的思路是从每个小矩形开始,向左右遍历,找到第一个比它矮的小矩形的位置,算出总宽度,乘该矩形高与当前记录的最大面积比较取最大。但无疑这样会造成超时。因此我们应该用较小时间复杂度的方法求取每个矩形可向左右延展宽度。
这里采用了单调栈的方法,遍历各个矩形,依次对此压入,压入的同时要维护栈的单调性。对于递增栈(栈底到栈顶递增)若要压入的元素小于当前栈顶元素,则弹出栈顶元素,同时可以确定栈顶元素向右最远延伸到要压入的元素的位置-1,直到要压入的元素满足栈的单调性则将其压入。遍历完所有矩形后,对于栈中剩余元素,其向右延伸的最远位置无疑为栈顶元素所在位置。经过一次遍历之后,其最右端位置便确定了。
然后使用递增栈反向遍历各个矩形,同样可以确定其可向左延伸的位置,唯一不同的是,要弹出的栈顶元素可向左延伸的最大位置为要压入元素的位置+1,而非-1,因为这里是反向遍历。
确定了每个矩形的延展宽度之后,便可以遍历以求取最大面积,这样时间复杂度可降低到线性级别。
代码:
#include <iostream>
using namespace std;
const int size=1e5+10;
int n,a[size],L[size],R[size],st[size];
int main(int argc, char** argv) {
while(scanf("%d",&n)&&n!=0)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int l=1,r=0;
for(int i=1;i<=n;i++)
{
while(l<=r&&a[st[r]]>a[i])
{
R[st[r]]=i-1;
r--;
}
st[++r]=i;
}
while(l<=r)
{
R[st[l]]=st[r];
l++;
}
for(int i=n;i>=1;i--)
{
while(l<=r&&a[st[r]]>a[i])
{
L[st[r]]=i+1;
r--;
}
st[++r]=i;
}
while(l<=r)
{
L[st[l]]=st[r];
l++;
}
long long ans=0;
for(int i=1;i<=n;i++)
{
long long temp=(R[i]-L[i]+1)*(long long)a[i];
ans=max(ans,temp);
}
printf("%lld\n",ans);
}
return 0;
}