堆栈、队列、单调栈、单调队列

例1

有n个人,按照1,2,3……的顺序依次进栈,判断能否以题目所给顺序出栈。

随进随判断能不能弹出。

#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
int n,a[maxn];
stack<int>st;
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i]; 
	int j=1;
	for(int i=1;i<=n;++i){
		st.push(i);
		while(!st.empty()&&st.top()==a[j]){
			st.pop();
			j++;
		}
	}
	if(!st.empty()) cout<<"No";
	else cout<<"Yes"; 
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

好串


牛牛认为如果一个串是好的当这个串能按照如下方法被构造出来:
一开始,有一个空串,然后执行0次或者若干次操作,每次操作将ab插入当前的字符串
根据上面的定义,ab, aabb, aababb都是好串,aab,ba,abbb并不是好串
现在给你一个字符串s,判断s是否是好串

简单的操作,可以把ab分别看作左右括号,也可以直接用差分数组和前缀和来维护。但若是好几种括号,就需要用栈进行维护。

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

[NOIP2004]合并果子

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

    每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

    因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

当两堆果子合并成一堆之后,再和其他果子堆合并的代价就是前面两个果子堆之和,所以要尽先让小代价的果子合并。

解法1:可以先将果子排序,后将合并的果子存入一个队列,然后循环比较没合并的果子和队列中果子的代价,把代价小的取出两个,继续存入队列,以此类推。细节方面,需要注意遵守原则:一次循环尽量把事情干完,不要拖泥带水,要不容易出现逻辑错误。比如合并需要n-1次,每一次都要从队列或原数组中取出共2个数,然后ans+他们的值之和。然后就是尽量用STL,减少因为head、tail指针出现错误的几率。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=100005,inf=0x7fffffff;
const double esp=1e-8;
int n,a[maxn],sav[5],ans;
queue<int>q1,q2;
//scanf("%d",&a[i]);
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	for(int i=1;i<=n;++i)
		q1.push(a[i]);
	for(int i=1;i<n;++i){
		for(int j=1;j<3;++j){
			if(q2.empty() || (!q1.empty() && q2.front()>q1.front())){
				sav[j]=q1.front();
				q1.pop();
			}else{
				sav[j]=q2.front();
				q2.pop();
			}
		}
		ans+=sav[1]+sav[2];
		q2.push(sav[1]+sav[2]);
	}
	
	cout<<ans;
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

蚯蚓


蛐蛐国里现在共有 n 只蚯蚓(n 为正整数)。每只蚯蚓拥有长度,我们设第 i 只蚯蚓的长度为 ai(i=1,2,… , ni=1,2,…,n),并保证所有的长度都是非负整数(即:可能存在长度为 0 的蚯蚓)。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数 p(是满足 0 < p < 1 的有理数)决定,设这只蚯蚓长度为 x,神刀手会将其切成两只长度分别为 ⌊px⌋和 x−⌊px⌋ 的蚯蚓。特殊地,如果这两个数的其中一个等于 0,则这个长度为 0 的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加 q(是一个非负整常数)。
蛐蛐国王希望知道这 m 秒内的战况。具体来说,他希望知道:

·m 秒内,每一秒被切断的蚯蚓被切断前的长度(有 m 个数);

·m 秒后,所有蚯蚓的长度(有 n + m 个数)。

思路:长蚯蚓被切割后一定比之后切割的蚯蚓切后长度要长。假设切后较长的部分为a,较短的部分为b,所以考虑可以用三个队列维护有序信息,即一个队列存从未被切过的蚯蚓,两个队列一个存a一个存b,则越早进入队列的蚯蚓越长。但还有一个问题:切割后新产生的蚯蚓的诞生时间不一样,也就是他们新增长的时间不一样,所以当求切割时蚯蚓的具体长度时我们需要进行计算才能求得。所以我们需要记录蚯蚓两次切割分别的时间,由此计算他们的生长时间。可用结构体实现。

还有一个方法:当将蚯蚓放入队列时进行倒算:比如一条蚯蚓在时刻10遭到切割,此时它的长度为100,则我们只考虑它在时刻0时的长度90。

deque

一种特殊的队列型数据结构,特殊在可以头尾进行操作。

头文件:#include<deque>

deque<type> d

从前面插入元素n:d.push_front(n);

从前面删除元素:d.pop_front();

从后面插入元素n:d.push_back(n);

从后面删除元素:d.pop_back();

将容器头尾翻转:只需要标记头尾在哪即可。

输出容器内元素个数和所有元素:

cout<<d.size()<<endl;

for(int i=0;i<d.size();++i)
        cout<<d.at(i)<<' ';

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

滑动窗口

给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位,你的任务是找出窗体在各个位置时的最大值和最小值。

拿一个序列举例:1 5 3 2 4 ,假设k为3,接下来用一个双向队列对最大值进行维护(最小值同理)。首先队列加入1;后队列加入5,由于5在后方且比1大,说明1没有机会再成为最大值了,于是1出队;后队列依次加入3、2,因为当5出队后他们是有可能成为新的最大值的;后加入4同理,但此时需要让5出队,因为窗口长度为3.由以上规则,结合deque容器的使用可以得出结果。

细节问题:如何判断是否因太靠前该被踢掉?可以通过结构体记录入队位置来实现;应该怎样通过单调性让前面不符合要求的元素出队呢?我们不应该从队首判断是否应该踢出元素,因为队首很可能是一个很小的值(对于要维护的最小值队列来说。最大值队列同理。),所以应该从队尾踢,而这就要求我们必须在踢出元素之后再将值插入队列末尾。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;
const int maxn=3000005,inf=0x7fffffff;
const double esp=1e-8;
struct nd{
	int num,val;
}a[maxn];
int n,k,big[maxn],sma[maxn];
deque<nd>maxx,minn;
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i].val);
		a[i].num=i;
		while(!maxx.empty() && maxx.back().val<=a[i].val)
			maxx.pop_back();
		while(!minn.empty() && minn.back().val>=a[i].val)
			minn.pop_back();
		maxx.push_back(a[i]);
		minn.push_back(a[i]);
		while(maxx.front().num<=i-k) maxx.pop_front();
		while(minn.front().num<=i-k) minn.pop_front(); //出现问题:如何判断是否该被踢掉? 
		if(i-k>=0) {
			big[i-k+1]=maxx.front().val;
			sma[i-k+1]=minn.front().val;
		}
	}
	for(int i=1;i<=n-k+1;++i){
		if(i!=1) cout<<' ';
		cout<<sma[i];
	}
	cout<<endl;
	for(int i=1;i<=n-k+1;++i){
		if(i!=1) cout<<' ';
		cout<<big[i];
	}
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

Look Up


Farmer John 的 N (1 <= N <= 100,000) 头奶牛,方便地编号为 1..N,再次排成一排。母牛 i 的身高为 Hi (1 <= Hi <= 1,000,000)。
每头奶牛都向左看那些指数较高的奶牛。如果 i < j 且 Hi < Hj,我们说奶牛 i“仰视”奶牛 j。对于每头奶牛 i,FJ 想知道奶牛 i 查找的第一头奶牛的索引。
注意:大约 50% 的测试数据会有 N <= 1,000。

做题之前一定先自己手动模拟,来更清楚地发现规律以便使用数据结构。比如1 4 9 2 3,这一组数应该怎样分析?既然都向后看,那不防从后向前模拟:3入队,没有仰望的牛;后2入队(因为后面可能会出现比2矮的牛),同时仰望3;9入队,此时9前方的牛由于9遮挡了视线无法看到2/3,此时2/3出队,9没有仰望的牛……以此类推。同时我们发现,只要有牛比之前的牛都高,就需要全部出队,才能让当前最高的牛入队;而未出队的牛中的队尾将被下一个入队牛仰望。这样看起来只需在队列尾部进行出入操作即可,那不防用栈维护即可。

#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
const int maxn=300005,inf=0x7fffffff;
const double esp=1e-8;
int n,a[maxn],ans[maxn],maxx,num;
struct nd{
	int num,val;
}x;
stack<nd>st;
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=n;i>=1;--i){
		x.num=i; x.val=a[i];
		while(!st.empty() && st.top().val<=a[i])
			st.pop();
		if(st.empty()) ans[i]=0;
		else ans[i]=st.top().num;
		st.push(x);
	}
	for(int i=1;i<=n;++i)
		printf("%d\n",ans[i]);
	return 0;
}

链接:https://ac.nowcoder.com/acm/problem/50965
来源:牛客网

Largest Rectangle in a Histogram


直方图是由在公共基线对齐的一系列矩形组成的多边形。矩形具有相同的宽度,但可能具有不同的高度。例如,左图显示了由高度为 2、1、4、5、1、3、3 的矩形组成的直方图,以单位为单位测量,其中 1 是矩形的宽度:



通常,直方图用于表示离散分布,例如文本中字符的频率。请注意,矩形的顺序(即它们的高度)很重要。计算直方图中与公共基线对齐的最大矩形的面积。右图显示了所描绘直方图的最大对齐矩形。

思路同上,只不过需要两头求。

细节问题:叕忘数据范围!long long杀我QAQ

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<deque>
using namespace std;
const int maxn=300005,inf=0x7fffffff;
const double esp=1e-8;
long long n,a[maxn],ansl[maxn],ansr[maxn],maxx,num;
struct nd{
	long long num,val;
}x;
stack<nd>sl,sr;
int main(){
	while(1){
		cin>>n;
		if(n==0) break;
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		for(int i=1;i<=n;++i){
			x.num=i; x.val=a[i];
//			cout<<1;
			if(i==1){
				sl.push(x);
				ansl[1]=1;
				continue;
			}
			while(!sl.empty()&&sl.top().val>=x.val)
				sl.pop();
			if(sl.empty()) ansl[i]=1;
			else ansl[i]=sl.top().num+1;
			sl.push(x);
		}
		for(int i=n;i>=1;--i){
			x.num=i; x.val=a[i];
//			cout<<1;
			if(i==n){
				sr.push(x);
				ansr[i]=n;
				continue;
			}
			while(!sr.empty()&&sr.top().val>=x.val)
				sr.pop();
			if(sr.empty()) ansr[i]=n;
			else ansr[i]=sr.top().num-1;
			sr.push(x);
		}
		long long sz=0;
		for(int i=1;i<=n;++i){
			sz=max(sz,(ansr[i]-ansl[i]+1)*a[i]);
		}
		cout<<sz<<endl;
		while(!sl.empty()) sl.pop();
		while(!sr.empty()) sr.pop();
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值