栈,队列,单调栈,单调队列

经典题

for循环让数字按顺序压入栈,然后里面while循环判断是否弹出栈,最后循环结束的时候看看栈是否为空,即该出栈顺序有没有问题。

#include<bits/stdc++.h>
using namespace std;
int a[1010];
int main()
{
	int n;
	cin>>n;
	stack<int>st;
	int t;
	cin>>t;
	while(t--)
	{
		for(int i=1;i<=n;i++) cin>>a[i];
		int pos=1;
		for(int i=1;i<=n;i++)
		{
			st.push(i);
			while(!st.empty()&&st.top()==a[pos])
			{
				st.pop();
				pos++;
			} 
		}
		if(st.empty()) cout<<"Yes"<<endl;
		else cout<<"No"<<endl; 
	}
} 

 例题1:

给你一个1->n的排列和一个栈,入栈顺序给定

你要在不打乱入栈顺序的情况下,对数组进行从大到小排序

当无法完全排序时,请输出字典序最大的出栈序列

思路:观察发现,当一个数后面没有比他大的数的时候他必出栈

#include<bits/stdc++.h>
using namespace std;
int a[1000010];
int maxn[1000010];
int main()
{
    int n;
    stack<int>st;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=n;i>=1;i--) maxn[i]=max(maxn[i+1],a[i]);
    for(int i=1;i<=n;i++)
    {
        st.push(a[i]);
        while(!st.empty()&&st.top()>maxn[i+1])
        {
            cout<<st.top()<<' ';
            st.pop();
        }
    }
    while(!st.empty())
    {
        cout<<st.top()<<' ';
        st.pop();
    }
}

 水题:

牛牛喜欢跟字符串玩耍,他刚刚学会了一个新操作,将一个字符串x插入另一个字符串y中(包括放在开头和结尾)
牛牛认为如果一个串是好的当这个串能按照如下方法被构造出来:
一开始,有一个空串,然后执行0次或者若干次操作,每次操作将ab插入当前的字符串

根据上面的定义,ab, aabb, aababb都是好串,aab,ba,abbb并不是好串

现在给你一个字符串s,判断s是否是好串

思路:对于a让他入栈,对于b,出一个a,如果此时栈为空则不是好串,最后遍历完看栈是否为空,不空不是好串。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    string s;
    cin>>s;
    int len=s.size();
    stack<char> st;
    for(int i=0;i<len;i++)
    {
        if(s[i]=='a') st.push(s[i]);
        else {
            if(st.empty()) 
            {
                cout<<"Bad"<<'\n';
                return 0;
            }else{
                st.pop();
            }
        }
    }
    if(st.empty()) cout<<"Good"<<'\n';
    else cout<<"Bad"<<'\n';
    return 0;
}

解法2:考虑到栈中只会有a,所以没必要用栈维护,只需要将a全部转化为1,然后b转化为-1,中途前缀和为负数直接不是好串,最后遍历完和为0是好串。

队列

例1:

合并果子

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

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

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

例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

思路:由于合并后的果子先后顺序一定是从小到大的,所以可以把合并后的果子放在另一个队列里,这样这个队列就自然是从小到大排序,然后每次询问两个队列的首元素谁小就拿谁出来。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
queue<int> q1,q2;
int a[100010];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++) q1.push(a[i]);
	ll ans=0;
	for(int i=1;i<=n-1;i++)//一共合并n-1次
	{
		int x[3];
		for(int j=1;j<=2;j++)
		{
			if(q2.empty()||!q1.empty()&&q1.front()<q2.front())
			{
				x[j]=q1.front();
				q1.pop();
			}else{
				x[j]=q2.front();
				q2.pop();
			}
		}
		ans+=x[1]+x[2];
		q2.push(x[1]+x[2]); 
	} 
	cout<<ans;
} #include<bits/stdc++.h>
using namespace std;
#define ll long long
queue<int> q1,q2;
int a[100010];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++) q1.push(a[i]);
	ll ans=0;
	for(int i=1;i<=n-1;i++)//一共合并n-1次
	{
		int x[3];
		for(int j=1;j<=2;j++)
		{
			if(q2.empty()||!q1.empty()&&q1.front()<q2.front())
			{
				x[j]=q1.front();
				q1.pop();
			}else{
				x[j]=q2.front();
				q2.pop();
			}
		}
		ans+=x[1]+x[2];
		q2.push(x[1]+x[2]); 
	} 
	cout<<ans;
} 

deque

例题:

思路:for循环遍历数组每一个数,让它进入maxn(minn)队列,比如最大值,如果新进入的值大于前面队列中有的值,则前面队列中比该数小的数都不可能成为最大值,因为有它在,最小值同理,注意进入队列的是数组下标,而且队列中的元素的值都是从大到小的(因为有“淘汰”),当i遍历到队列首元素的后面时,首元素出队。

#include<bits/stdc++.h>
using namespace std;
deque<int> maxn,minn;
int a[1000010];
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	maxn.push_back(1);
	minn.push_back(1); 
	for(int i=2;i<=n;i++)
	{
		if(i-k>=minn.front()) minn.pop_front();
		if(a[i]<a[minn.back()])
		{
			while(a[i]<a[minn.back()]&&!minn.empty())
			{
				minn.pop_back();
				if(minn.empty()) break;
			}
		}
		minn.push_back(i);
		if(i>=k)
		{
			cout<<a[minn.front()]<<' ';
		}
	}
	cout<<'\n';
	for(int i=2;i<=n;i++)
	{
		if(i-k>=maxn.front()) maxn.pop_front();
		if(a[i]>a[maxn.back()])
		{
			while(a[i]>a[maxn.back()]&&!maxn.empty())
			{
				maxn.pop_back();
				if(maxn.empty()) break;
			}
		}
		maxn.push_back(i);
		if(i>=k)
		{
			cout<<a[maxn.front()]<<' ';
		}
	}
}

变式:

给定一个数组,求数组中每个元素右边第一个比它大的元素。

思路:跟滑动窗口一样的题,用一个deque来维护可能被仰望的牛,for循环从后往前,如果遍历到当前的牛比deque中最左边的牛高,那最左边这头牛不可能成为被仰望的牛了,就一直这样循环淘汰下去,然后队列里第一头牛就是遍历到的当前牛的仰望对象。

#include<bits/stdc++.h>
using namespace std;
deque<int> q;
int a[100010];
int ans[100010];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	q.push_front(n);
	for(int i=n;i>=1;i--)
	{
		while(!q.empty()&&a[i]>=a[q.front()])
		{
			q.pop_front();
		}
		if(!q.empty())
		{
			ans[i]=q.front();
		}else{
			ans[i]=0;
		}
		q.push_front(i);
	}
	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}

可以发现前两道题队列中的元素都是单调的(淘汰机制),所以称该题型为单调队列,单调栈

Plus版:

 思路:对于每一个块,我们找到左边第一个比它小的块和右边第一个比它小的块,然后它能扩展到最大的矩形面积就是(i-zm[i]+ym[i]-i-1)*a[i],然后这种左边第一个某某,右边第一个某某就是上面两个例题的做法了。

#include<bits/stdc++.h>
using namespace std;
deque<int>q1,q2;
long long a[100010];
int zm[100010];
int ym[100010];
int main()
{
	int n;
	while(cin>>n)
	{
		if(n==0) break;
		while(!q1.empty()) q1.pop_back();
		while(!q2.empty()) q2.pop_back();
		for(int i=1;i<=n;i++) cin>>a[i];
		q1.push_back(1);
		zm[1]=0;
		ym[n]=n+1;
		q2.push_front(n);
		for(int i=2;i<=n;i++)
		{
			while(!q1.empty()&&a[q1.back()]>=a[i])
			{
				q1.pop_back();
			}
			if(!q1.empty()) zm[i]=q1.back();
			else zm[i]=0;
			q1.push_back(i);
		}
		for(int i=n-1;i>=1;i--)
		{
			while(!q2.empty()&&a[i]<=a[q2.front()])
			{
				q2.pop_front();
			}
			if(!q2.empty()) ym[i]=q2.front();
			else ym[i]=n+1;
			q2.push_front(i);
		}
		long long maxn=-1;
		for(int i=1;i<=n;i++)
		{
			maxn=max(maxn,(i-zm[i]+ym[i]-i-1)*a[i]);
		}
		cout<<maxn<<endl;
	}
}

 单调栈单调队列可以用来求左边(右边)第一个某某。

然而这种求法都是O(n)复杂度的,因为每个元素只有一次进出队列的机会。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值