单调栈算法 Largest Rectangle in a Histogram

本文介绍了单调栈算法,特别是在解决最大长方形面积问题中的应用。文中通过AC代码展示了如何使用单调栈,并提到了单调队列作为单调栈的升级版,讨论了单调队列的时间复杂度、适用范围和在动态规划中的优化作用。同时,给出了一个使用单调队列解决最大子序和问题的代码示例。
摘要由CSDN通过智能技术生成

今天学了一下单调栈算法,给大伙分享下心得。
嘻嘻
定义:

单调栈,顾名思义,是栈内元素保持一定单调性(单调递增或单调递减)的栈。这里的单调递增或递减是指的从栈顶到栈底单调递增或递减。既然是栈,就满足后进先出的特点。与之相对应的是单调队列。

我的理解是将入栈元素按照某种单调顺序排列,在遇到逆序的时候将栈顶元素弹出,直到栈为空。

然后看一看例题:Largest Rectangle in a Histogram

链接:https://ac.nowcoder.com/acm/contest/1005/C
来源:牛客网

题目描述 
A histogram is a polygon composed of a sequence of rectangles aligned at a common base line. The rectangles have equal widths but may have different heights. For example, the figure on the left shows the histogram that consists of rectangles with the heights 2, 1, 4, 5, 1, 3, 3, measured in units where 1 is the width of the rectangles: 

Usually, histograms are used to represent discrete distributions, e.g., the frequencies of characters in texts. Note that the order of the rectangles, i.e., their heights, is important. Calculate the area of the largest rectangle in a histogram that is aligned at the common base line, too. The figure on the right shows the largest aligned rectangle for the depicted histogram.
输入描述:
The input contains several test cases. Each test case describes a histogram and starts with an integer n, denoting the number of rectangles it is composed of. You may assume that 1 \leq n \leq 1000001≤n≤100000. Then follow n integers h1\dots hnh1…hn, where 0 \leq h_i \leq 10000000000≤h 
i
​	
 ≤1000000000. These numbers denote the heights of the rectangles of the histogram in left-to-right order. The width of each rectangle is 1. A zero follows the input for the last test case.
输出描述:
For each test case output on a single line the area of the largest rectangle in the specified histogram. Remember that this rectangle must be aligned at the common base line.
示例1
输入
复制
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出
复制
8
4000
说明
Huge input, scanf is recommended.

ac代码如下:

#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N = 100010;
ll a[N], s[N], w[N],n, ans;

 ll read()
{
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch > '9' || ch < '0')
	{
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0'&&ch <= '9')
	{
		x = x * 10 + (ch & 15);
		ch = getchar();
	}
	return x * f;
}

int main()
{
	while (cin>>n,n)
	{
		memset(a, 0, sizeof a);
		memset(s, 0, sizeof s);
		memset(w, 0, sizeof w);
		
		int p = 0;

		for (int i = 1; i <= n; i++)
		{
			a[i] = read();
		}	
		ans = 0;
		for (int i = 1; i <= n+1; i++)
		{
			if (a[i] > s[p])
			{
				s[++p] = a[i], w[p] = 1;
			}
			else
			{
				int width = 0;
				while (s[p] > a[i]) { 
					width += w[p];
					ans = max(ans, (long long)width*s[p]);
					p--;
				}
				s[++p] = a[i], w[p] = width + 1;
			}
		}
		cout << ans << endl;
	}

	system("pause");
	return 0;
}

xiexie
关于单调栈的另一种写法:
思路都在代码里了

#include <iostream> 
#include <algorithm>
#include <queue>
#include <stack>

using namespace std;

typedef long long ll;
const int N =100010;
int n;
int h[N],q[N],l[N],r[N];
void get(int bound[N])
{
	int tt=0;
	h[0]=-1;
	for(int i=1;i<=n;i++)
	{
		while(h[q[tt]]>=h[i])tt--;//找到比当前高度小的
		bound[i] =q[tt];
		q[++tt]=i;//加入栈中
	}
}

int main()
{
	while(cin>>n,n)
	{
		for(int i=1;i<=n;i++)
		cin>>h[i];
		get(l);//求左边每一位第一个比他小=的元素 的位置 
		reverse(h+1,h+n+1);
		get(r);//同理找右边第一个比他小=的每一位元素 的位置 
		ll res=0;//可能会爆所以
		for(int i=1,j=n;i<=n;i++,j--)
			res = max(res,h[i]*((ll)n-l[j]-r[i]));//高度乘上左边 到 右边的距离  
			
		cout<<res<<endl;
			
	}	
	return 0;
}

看了秦大佬的博客,发现单调队列是单调栈的升级版。

什么是单调队列?
单调队列顾名思义,就是具有单调性质和队列性质的数据结构.
单调队列时间复杂度是多少?为什么是这样的?
单调队列可以保证,对于任何一个数而言,只会在队列中出现一次,一旦这个数对于最后答案没有贡献了,就会及时地将它删除.

一般来说,入队和出队操作满足以下两点.

  1. 入队操作:对于一个点而言,如果说它加入队列后满足队列的单调性质,那么我们就可以入队.
  2. 出队操作:对于一个点而言,如果说新加入的点,比它更加具有潜力,潜力一般指(拓展性更强,生存能力更高,节点入队时间短.)

因此单调队列的复杂度是我们除了常数复杂度O(1)O(1),O(log)O(log)复杂度级别,第三位最喜欢的线性复杂度O(n)O(n),因为这种算法往往代码短,思路好想,符合人类思维.
所以单调队列算法发明者相信,码量适中,思路精简,结构典型的单调队列,一定可以给你的程序员之旅一个有力的帮助.

单调队列适用于那些范围
单调队列,其实是单调栈的一个升级plus版本,或者说是具有[l,r]区间性质的单调栈.(注:单调栈一般来说是[0,r]类型的)

换句话来说,对于单调栈可以做出来的题目,基本上单调队列是可以操作出来的,而且请注意一点,单调队列适用范围更加广阔.
一般来说面对具有区间最优性质问题,并且具有两大性质,单调+队列的数据结构单调队列,往往都可以登上程序的数据结构优化的精彩舞台.

还有单调队列也是动态规划算法一个必备的优化手段,在NOIP,Acm,大公司面试题目中频频出现,所以我们有必要学会这种看似高端大气上档次的数据结构,实际上简易易懂的数据结构.

单调队列算法步骤
对于一个数而言,它可以从队尾入队,必须满足题目的特定条件
对于一个队头的数而言,如果说新来的数,不仅是新来的具有潜力,而且又自身价值还比它价值高,那么不用说队头出队.
总而言之,队列的单调条件,性质如何设置,是我们解题的关键.

作者:秦淮岸灯火阑珊
链接:https://www.acwing.com/blog/content/150/

然后我贴一个我遇到的一个“单调队列”题(最大子序和)的代码:

#include <iostream>
#include <algorithm>

using namespace std;
const int N =300010;
int   q[N],sum[N];
int n,m;
int main()
{
    cin >>n >>m;
    for(int i=1;i<=n;i++)
    {
        cin>>q[i];
        sum[i] = sum[i-1]+q[i];
    }
   
    int l=1,r=1,ans=0;
    q[l] = 0;
    for(int i = 0;i<=n;i++)
    {
        while(l<=r&&q[l]<i-m)l++;
        ans = max(ans,sum[i]-sum[q[l]]);
        while(l<=r&&sum[q[r]]>=sum[i])r--;
        q[++r]=i;
    }
    cout<<ans<<endl;
    return 0;
}

更多信息可参考:秦淮岸灯火阑珊的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值