WEEK5 周记 作业——单调栈_最大矩形

WEEK5 周记 作业——单调栈_最大矩形

一、题意

1.简述

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
在这里插入图片描述

2.输入格式

输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。

3.输出格式

对于每组测试数据输出一行一个整数表示答案。

4.样例

Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Output

8
4000

二、算法

主要思路

如果我们确定了一个高度,那么我们必须使左端点尽量靠左,右端点尽量靠右才能使直方图面积尽量大,还需要注意的是,左右端点之间的“柱子”的高度必须 ≤ \le 我们确定的高度。
现在我们只需找到某一个柱子其左边和右边第一个比它高的柱子的位置,利用单调栈可以很好地解决。

单调栈可以视为一种栈,满足单侧开口,FILO的规则。不过单调栈还增加了一条规则:必须保持栈内元素是单增/单减(可以自己设定)的。以单增栈为例,当往栈内压入元素时,如果栈顶元素比将要压入的元素小,就可以压入;如果比它大,我们就需要将栈顶的元素弹出,再去比较新的栈顶元素与要压入元素的大小,这样不断比较不断弹出,直到栈空或者栈顶元素要比压入的元素小再压入。
基于单调栈的这个特性,我们可以找到某一个元素在左/右边第一个小/大于他的元素。以找右边第一个小于他的元素为例,我们用单增栈,从左往右将数组中的元素压栈,当遇到一个元素要大于栈顶元素,此时栈顶元素要弹出,而这时也说明了栈顶元素遇到了右边第一个小于他的元素,就是这个要压栈的元素;弹出之后还可以继续比较后面的栈顶元素,如果还是比栈顶元素小,那么又可以为新的栈顶元素确定他的右边第一个小的元素了。这样遍历一遍数组,实际上就将数组中所有元素的右边第一个小于它的元素给找到了,这样的时间复杂度是 O ( n ) O(n) O(n)的。

回到这个题,利用单调栈,我们可以为每一个柱子找到其右边第一个低于其高度的柱子的位置,同样,从右往左再遍历一次,我们又可以获取每一个柱子左边第一个低于其高度的柱子的位置,这样我们就能为每一个柱子求得其所能画得的直方图的最大面积。求一个max的得到这些面积中最大的。该题结束。


题外

本题当时出错的地方:在临时变量处爆了int的精度。
给我的debug提了一个醒:数据的精度和范围一定要仔细,考虑全面再确定用什么类型存储,这里看着数据的范围似乎用int就能搞定,但实际上很多地方都有可能超出int,比如最后算每一个矩形面积的时候的代码:

long long int max=0;	
for(i=0;i<n;i++)
{
	long long temp=(r[i]-l[i]-1)*h[i];
	if(max<temp)max=temp;
}

long long temp=(r[i]-l[i]-1)*h[i]这里看似考虑了超出long long 的情况,但实际上还是漏掉了int型的临时变量,即当乘积的双方都是int型时,返回的也是int型的临时变量,然后再赋给long long的temp,所以,这里应该将乘积中的某一方换成long long类型的变量,可以通过强制类型转换,也可以在定义的时候就直接用long long定义。

三、代码

#include<iostream>
#include <cmath>
#include<sstream>
#include<string>
#include<cstdlib>
#include<cstring>
using namespace std;
int n;
long long int h[100010];
int st[100010];//单增 
int top;
int l[100010];
int r[100010];
int main()
{
	while(1)
	{
		scanf("%d",&n);
		if(n==0)break;
		top=-1;
		memset(h,0,sizeof(h));
		memset(st,0,sizeof(st));
		memset(l,0,sizeof(l));
		memset(r,0,sizeof(r));
		int i;
		for(i=0;i<n;i++)
			scanf("%d",&h[i]);
		
		for(i=0;i<n;i++)
		{
			while(top>=0&&h[st[top]]>h[i])
			{
				r[st[top]]=i;
				top--;
			}
			top++;
			st[top]=i;//存储在h中的索引 
		}
		while(top>=0)
		{
			r[st[top]]=n;
			top--;
		}
			
		top=n;
		for(i=n-1;i>=0;i--)
		{
			while(top<=n-1&&h[st[top]]>h[i])
			{
				l[st[top]]=i;
				top++;
			}
			top--;
			st[top]=i;//存储在h中的索引 
		}
		while(top<=n-1)
		{
			l[st[top]]=-1;
			top++;
		}
			
		long long int max=0;	
		for(i=0;i<n;i++)
		{
			long long temp=(r[i]-l[i]-1)*h[i];
			if(max<temp)max=temp;
		}
		printf("%lld\n",max);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值