单调栈

单调栈

唠嗑:寒假遇到过,当时没能弄懂,昨天在算法进阶指南看到,再一次理解之后感觉对它的认识更近了一步,写一写记录一下,也理一下思路。

分类:

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

模板:

单调栈的题目解决的代码格式比较相似,下面是单调栈主体部分的模板。

伪代码:
    
stack<int> st;
//此处一般需要给数组最后添加结束标志符,一般在st的最后一个元素加入,为了让栈中的元素可以完全弹出,这得结合题目来看,不清楚的看后面题目。
for (遍历这个数组)
{
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{
		入栈;
	}
	else
	{
		while (栈不为空 && 栈顶元素小于当前元素)
		{
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

应用:

1.

最基础的应用就是给定一组数,针对每个数,寻找它和它右边第一个比它大的数之间有多少个数。

题目:

链接.

题意:

有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。

思路:

这就是求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。

代码:
#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 8e4+10,M=20;
const int inf=0x3f3f3f3f;
int n,m,t,x;

int main()
{
	int i,n,top,a[N]; //top指向栈顶元素 
	ll ans; //存储最终结果 
	stack<int> st; //st为栈,每次入栈的是每头牛的位置 
	while(~scanf("%d",&n))
	{
		while(!st.empty()) st.pop(); //清空栈 
		for(i=0;i<n;i++)
			scanf("%d",&a[i]);
		a[n]=inf; //将最后一个元素设为最大值 
		ans=0;
		for(i=0;i<=n;i++)
		{
			if(st.empty()||a[i]<a[st.top()])
			{ //如果栈为空或入栈元素小于栈顶元素,则入栈 
				st.push(i);
			}
			else 
			{
				while(!st.empty()&&a[i]>=a[st.top()])
				{ //如果栈不为空并且栈顶元素不大于入栈元素,则将栈顶元素出栈 
					top=st.top(); //获取栈顶元素 
					st.pop();     //栈顶元素出栈 
					//这时候也就找到了第一个比栈顶元素大的元素 
					//计算这之间牛的个数,为下标之差-1 
					ans+=(i-top-1);	
				}
				st.push(i); //当所有破坏栈的单调性的元素都出栈后,将当前元素入栈 
			}
		}
        cout << ans << endl;
    }
	return 0;
}

2.

给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。

题目:

链接.

题意:

image-20210512151438699

题解:

这里引用一下《算法进阶指南》里面的讲解,觉得讲的真心不错。
image-20210512152206755

代码:
#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 1e5+10,M=20;
const int inf=0x3f3f3f3f;
ll n,m,t,x;
ll arr[N];

int main(){
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
   while(cin>>n){
       if(n==0)
           break;
       stack<ll> st;
       arr[n + 1] = -1;
       for (int i = 1; i <= n;i++){
       cin >> arr[i];
   }
   ll ans = 0;
   for (int i = 1; i <= n + 1; i++)
   {
       if (st.empty() || arr[st.top()] <= arr[i])
       {
           st.push(i);
       }
       else
       {
           while (!st.empty()&&arr[st.top()] > arr[i])
           {
               t=st.top();
               st.pop();
               ll temp = (i - t) * arr[t];
               if(temp>ans)
                   ans = temp;
                      }
           st.push(t);
           arr[t] = arr[i];
       }
       }
       cout << ans<< endl;
   }

      // system("pause");
   return 0;
}

3.

给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

题目:

链接.

题意:

求序列中的最小值乘以这个序列的和的值最大,是典型的单调栈的应用。一般的思路是求每个数字所在的能使其值为区间最小值的最大区间,然后求出区间元素和乘以该值并更新结果的最大值。普通的做法时间复杂度为O(n^2),用单调栈可以达到O(n)。

题解:

用一个单调递减栈,如果栈为空或入栈元素大于等于栈顶元素,则入栈,否则将破坏栈的单调性,则将栈顶元素出栈,直到栈为空或碰到第一个小于等于入栈元素的元素。然后将最后一次出栈的栈顶元素入栈,并将其向左右拓展,并更新其对应的值。最后一次出栈的栈顶元素就是当前入栈元素可以向左拓展到的最大距离。

代码:
#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 1e5+10,M=20;
const int inf=0x3f3f3f3f;
int n,m,t,x;

int main()
{
	int i,n,pos1,pos2; //pos1和pos2记录区间的开始和结束位置 
	//tmp为临时变量,记录区间内的和;top指向栈顶元素;ans为结果;sum为前缀和 
    ll tmp,top,ans,a[100010],sum[100010];
	stack<int> st; //单调栈,记录元素位置 
	while(~scanf("%d",&n))
	{
		while(!st.empty()) st.pop(); //清空栈 
		sum[0]=0;
		for(i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			sum[i]=sum[i-1]+a[i]; //计算前缀和 
		}			
		a[n+1]=-1; //将最后一个设为最小值,以最后让栈内元素全部出栈 
		ans=0;
		for(i=1;i<=n+1;i++)
		{
			if(st.empty()||a[i]>=a[st.top()])
			{ //如果栈为空或入栈元素大于等于栈顶元素,则入栈 
				st.push(i);
			}
			else 
			{
				while(!st.empty()&&a[i]<a[st.top()])
				{ //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈 
					top=st.top();
					st.pop();					
					tmp=sum[i-1]-sum[top-1]; //计算区间内元素和 
					tmp*=a[top]; //计算结果 
					if(tmp>=ans) 
					{ //更新最大值并记录位置 
						ans=tmp;
						pos1=top;
						pos2=i;
					}
				}
				st.push(top); //将最后一次出栈的栈顶元素入栈 
				a[top]=a[i]; //将其向左向右延伸并更新对应的值 
			}
		}
		printf("%lld\n",ans);
		printf("%d %d\n",pos1,pos2-1);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值