单调栈_最大矩形 & 单调队列_滑动窗口

问题描述 ------最大矩形

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是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

解题思路以及关键代码

初始化

	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);//数据很多 用scanf速度快
	memset(a_l+1,0,n); //将左侧第一个比它小的位置初始化为0
	memset(a_r+1,n+1,n);//将右侧第一个比它小的位置初始化为n+1
	a[0] = -1 , a[n+1] = -1; //为了将栈中元素全部弹出

维护一个单调栈,当 栈非空 并且 当前元素小于栈顶元素时,将栈顶元素依次弹出,并且对其右侧第一个比它小的位置 a_r[st.top()]=i;

	for(int i=1;i<=n+1;i++)
	{
		while(!st.empty()&&a[st.top()]>a[i])
		{
			a_r[st.top()]=i;
			st.pop();
		}
		if(st.empty()||a[st.top()]<=a[i])
		 //if 其实可以省略,因为while后一定满足该条件,此处为了方便理解
		{
			st.push(i);
		}
	}

再倒叙来一遍,相当于找左边能到达的最远区域,本质是一样的。

全部代码

#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring>
using namespace std;
long long a[100005];
long long a_r[100005];
long long a_l[100005];
long long ans;
int main()
{
	int n;
	while(cin>>n&&n)
	{
		ans = 0;
		stack<int> st;
		for(int i=1;i<=n;i++)
			scanf("%lld",&a[i]);
		memset(a_l+1,0,n);
		memset(a_r+1,n+1,n);
		a[0] = -1 , a[n+1] = -1; //为了将栈中元素全部弹出
		
		for(int i=1;i<=n+1;i++)
		{
			while(!st.empty()&&a[st.top()]>a[i])
			{
				a_r[st.top()]=i;
				st.pop();
			}
			if(st.empty()||a[st.top()]<=a[i])
			{
				st.push(i);
			}
		}
		st.pop(); // a[n+1] = -1; //为了将栈中元素全部弹出 最后还在stack里面 
		for(int i=n;i>=0;i--)
		{
			while(!st.empty()&&a[st.top()]>a[i])
			{
				a_l[st.top()]=i;
				st.pop();
			}
			if(st.empty()||a[st.top()]<=a[i])
			{
				st.push(i);
			}
		}
		
		for(int i=1;i<=n;i++)
		{
			if(a[i]*(a_r[i]-a_l[i]-1)>ans) 
	//因为统计的是两侧第一个比它小的位置 所以面积=a[i]*(a_r[i]-a_l[i]-1)
				ans=a[i]*(a_r[i]-a_l[i]-1);
		}
		cout<<ans<<endl;
	}
}

问题描述--------滑动窗口

ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
在这里插入图片描述

Input

输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。

Output

输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

解题思路以及关键代码

先顺序维护一个单调递增的双向队列(存储下标)。
首先将1~k-1号元素push进队列中并维护单增队列,如果队尾元素大于当前元素,则一直pop队尾元素直到不满足条件。

for(int i=1;i<=k-1;i++) //初始化 
{   
	while(!q.empty()&&a[q.back()]>a[i])
	{
		 q.pop_back();
	} 
	if(q.empty()||a[q.back()]<=a[i])
	{
		q.push_back(i);
	}
  }

然后从k到n号元素开始,每次先维护队列的单调性,然后放入i号元素并且维护窗口的大小,具体做法为判断(i - 队首元素存储的下标)是否 > k,(因为队首元素一定是当前单调队列最先进来的元素)超过则pop队首元素 然后将队首元素存到a_min[i]中。

for(int i=k;i<=n;i++)//窗口从k开始向右移动 维护一个单增队列
{
	    while(!q.empty()&&a[q.back()]>a[i])   //先维护单调性
	{
		q.pop_back();
	} 
	    if(q.empty()||a[q.back()]<=a[i])
	{
		q.push_back(i);
	}
    	while(!q.empty()&&(i-q.front())>=k) //再维护窗口的大小 保证此时队列中都落在窗口中 
    {
    	q.pop_front();
	} 
	    a_min[i]=q.front();
}

同理,维护一个单减队列,重复上述操作。(前提把deque清空 clear())

全部代码

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int a[1000005];
int a_min[1000005];
int a_max[1000005];
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)   
		scanf("%d",&a[i]);  //数据多 用scanf 
	deque<int> q;           //储存下标
    for(int i=1;i<=k-1;i++) //初始化 
    {   
		while(!q.empty()&&a[q.back()]>a[i])
		{
			 q.pop_back();
		} 
		if(q.empty()||a[q.back()]<=a[i])
		{
			q.push_back(i);
		}
    }
	for(int i=k;i<=n;i++)//窗口从k开始向右移动 维护一个单增队列
    {
        while(!q.empty()&&a[q.back()]>a[i])   //先维护单调性
		{
			 q.pop_back();
		} 
        if(q.empty()||a[q.back()]<=a[i])
		{
			q.push_back(i);
		}
        while(!q.empty()&&(i-q.front())>=k) //再维护窗口的大小 保证此时队列中都落在窗口中 
        {
        	q.pop_front();
		} 
        a_min[i]=q.front();
    }
	q.clear(); //deque 可以清空队列 
	for(int i=1;i<=k-1;i++)
    {   
		while(!q.empty()&&a[q.back()]<a[i])
		{
			 q.pop_back();
		} 
        if(q.empty()||a[q.back()]>=a[i])
		{
			q.push_back(i);
		}
    }
	for(int i=k;i<=n;i++)//窗口从k开始向右移动 维护一个单增队列
    {
        while(!q.empty()&&a[q.back()]<a[i])   //先维护单调性
		{
			 q.pop_back();
		} 
        if(q.empty()||a[q.back()]>=a[i])
		{
			q.push_back(i);
		}
        while(!q.empty()&&(i-q.front())>=k) //然后维护窗口的大小  保证此时队列中都落在窗口中 
        {
        	q.pop_front();
		} 
        a_max[i]=q.front();
    }
	for(int i=k;i<=n;i++) 
		cout<<a[a_min[i]]<<" ";
    cout<<endl;
    for(int i=k;i<=n;i++) 
		cout<<a[a_max[i]]<<" ";
    cout<<endl;
    return 0;	
}

关于—单调栈以及单调队列的总结

单调队列的维护过程与单调栈相似
•区别在于
• 单调栈只维护一端(栈顶), 而单调队列可以维护两端(队首和队尾) 
• 单调栈通常维护 全局 的单调性, 而单调队列通常维护 局部 的单调性
• 单调栈大小没有上限, 而单调队列通常有大小限制
(由于单调队列 可以队首出队 以及 前面的元素一定比后面的元素先入队 的
性质,使得它可以维护局部的单调性,当队首元素不在区间之内则可以出队)

关于复杂度,遍历一遍,每个元素只访问一遍,所以是O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值