单调栈和单调队列问题

单调队列和单调栈都是在一定范围内排列有序(递增或者递减排列)的,不同点在于队列和栈在操作上的一些区别,比如进出顺序等。

为什么引入单调队列和单调栈呢?
主要是为了解决一些现实问题,比如求解一段数内最大最小数(单调队列–头出尾进),求解一个数左边或者右边第一个比其大/小的数(单调栈)。

单调栈相关问题
问题描述
求一个数组中左边第一个比他大的数。
暴力遍历的复杂度是O(N^2),而使用单调栈的复杂度可以降为O(N),空间也是S(N)【每个数最多进栈出栈一次】。

vector<int> get(vector<int> a, int len)
{
	vector<int> res(len, -1);
	stack<int> sta;
	sta.push(0);
	for (int i = len-1; i >=0; i--)
	{
		// 当前元素大于栈顶元素出栈,当前元素是第一个大于栈顶元素的元素
		while (!sta.empty() && a[i]>a[(sta.top())])
		{
			res[(sta.top())] = a[i];
			sta.pop();
		}
		// 当前元素小于栈顶元素进栈,维持一个递减栈
		sta.push(i);
	}
	return res;
}

单调队列相关问题:
给定一个数列,从左至右输出每个长度为m的数列段内的最小数和最大数。
数列长度:N<=1000,m<=N

  • 解法1:
    暴力解法,那就是从数列的开头,将长度为m的窗口放上去,找到这最开始的m个数的最大值,然后窗口向后移一个,继续找到m个数中的最大值。
    这种方法每求一次m个数中的最大值f(i),都要进行m-1次的比较,复杂度为O(Nm)

  • 解法2:
    使用单调队列,
    上面的解法在暴力枚举的过程中,有一个地方是重复比较了,就是在找当前的f(i)的时候,i的前面其它m-1个数在算f(i-1)的时候我们就比较过了。
    那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。答案是可以,这就要用到单调队列。

    单调队列实现的大致过程:
    1、维护队首(如果队首已经在当前元素的m个之前,则队首后移,head++)
    2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态,保持单调性)

    此时每个元素最多入队/出队1次,复杂度降为O(N),空间也是S(N)。

简单举例应用 数列为:6 4 10 10 8 6 4 2 12 14 N=10,K=3; 那么我们构造一个长度为3的单调递减队列:
首先,那6和它的位置0放入队列中,我们用(6,0)表示,每一步插入元素时队列中的元素如下
插入6:(6,0);
插入4:(6,0),(4,1);
插入10:(10,2);
插入第二个10,保留后面那个:(10,3);
插入8:(10,3),(8,4);
插入6:(10,3),(8,4),(6,5);
插入4,之前的10已经超出范围所以排掉:(8,4),(6,5),(4,6);
插入2,同理:(6,5),(4,6),(2,7);
插入12:(12,8);
插入14:(14,9);
那么f(i)就是第i步时队列当中的首元素:6,6,10,10,10,10,8,6,12,14
同理,最小值也可以用单调队列来做。

单调是一种思想,当我们解决问题的时候发现有许多冗杂无用的状态时,我们可以采用单调思想,用单调队列或类似于单调队列的方法去除冗杂状态,保存我们想要的状态。

求最小数代码(直接用数组模拟单调队列):

struct node
{
    int x,y;
}v[1000]; //x表示值,y表示位置 可以理解为下标

N, m, mMin[1000];

void getmin(int* a)
{
	// 默认起始位置为1 因为插入是v[++tail]故初始化为0
    int i,head=1,tail=0;
    
    // 根据题目 前m-1个先直接进入队列
    for(i=1; i<m; i++)
    {
        while(head<=tail && v[tail].x>=a[i]) tail--;
        v[++tail].x = a[i], v[tail].y = i;
    }
    for(; i<=N; i++)
    {
    	// 当前元素比队尾元素小则删除队尾元素
        while(head<=tail && v[tail].x>=a[i]) tail--;
        // 在队尾插入新元素并维持单调增队列
        v[++tail].x = a[i], v[tail].y = i;
        // 把已经超出范围的从head开始排出
        while(v[head].y < i-m+1) head++;
        //  每个队首则是目前m个数的最小值
        mMin[i-m+1] = v[head].x;       
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值