单调递增队列(全过程图文实现 另附习题)

什么是单调队列,有什么用?

不妨用一个问题来说明单调队列的作用和操作:
不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
最直接的方法:普通队列实现缓存数组。
进队出队都是O(1),一次查询需要遍历当前队列的所有元素,故O(n)。

单调队列的运用

假设数列:7 8 1 1 4 3 2
现在我们要求出以3个元素为区间大小的条件下,每个区间的最小值。
初步判断:
第一个区间:7 8 1最小值为 1
第二个区间:8 1 1最小值为 1
第三个区间:1 1 4最小值为 1
第四个区间:1 4 3最小值为 1
第五个区间:4 3 2最小值为 2

单调队列如何实现?

从单调队列的定义我们知道,数列中的每个元素都要进队列,那么队列有进自然就有出,那出队又是怎么实现的呢?也许不应该称为出队,称为 覆盖 会更为恰当。

我们需要不断更新以下几点(变量定义):
①、已入队元素数量 i
②、队头元素在数列中的序号 start
③、队尾元素在数列中的序号 end
④、区间大小m

输出队头元素要满足三个条件:
①、该队列符合递增。
②、i - start < m。
③、i >= m 。


队列实现步骤

为了更清晰展现实现过程,下文将用变量来代替部分文字。
若对变量定义不理解的朋友,请看上方 变量定义 。
数列
①、首元素入队:7
队列
变量: i = 1, start = 1, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i < m ,不符合
结果:不输出队头元素 7 。


②、入队:8
第二个元素
变量: i = 2, start = 1, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i < m ,不符合
结果:不输出队头元素 7 。


③、入队:1
第三个元素
变量: i = 3, start = 1, m = 3
分析:
1、该队列不符合递增
操作:舍弃队列中比第三个元素1大的元素7 8。
队列
变量: i = 3, start = 3, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
结果:输出队头元素 1 。


④、入队:1
队列
变量: i = 4, start = 3, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
结果:输出队头元素 1 。


⑤、入队:4
队列
变量: i = 5, start = 3, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
结果:输出队头元素 1 。


⑥、入队:3
队列
变量: i = 6, start = 3, m = 3
分析:
1、该队列不符合递增
操作:舍弃队列中比第六个元素3大的元素 4 。
队列
变量: i = 6, start = 3, m = 3
分析:
1、该队列符合递增,符合。
2、i - start == m ,不符合
队列
变量: i = 6, start = 4, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
结果:输出队头元素 1 。


⑦、入队:2
队列
变量: i = 7, start = 4, m = 3
分析:
1、该队列不符合递增
操作:舍弃队列中比第七个元素2大的元素 3 。
队列
变量: i = 7, start = 4, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,不符合
队列
变量: i = 7, start = 5, m = 3
分析:
1、该队列符合递增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
结果:输出队头元素 2 。

最终输出:1 1 1 1 2
与初步判断输出相同!
完成!


习题

Problem One:m区间最小值
题目描述
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。

输入格式
第一行两个数n,m。
第二行,n个正整数,为所给定的数列。
输出格式
n行,第i行的一个数ai,为所求序列中第i个数前m个数的最小值。

输入输出样例
输入
6 2
7 8 1 4 3 2
输出
0
7
7
1
1
3
说明/提示
【数据规模】
m ≤ n ≤ 2000000
ai ≤ 3×107


洛谷测试平台
运用单调递增队列的思想来完成本题。
①、创建结构数组,保存每项数列的数据及排列顺序。

//维护一个单调递增的动态队列:start~end
struct node{
	int x;	//数据大小
	int y;	//排列顺序
}q[2000010];

②、不断维护一个单调递增的动态队列。
注意:队列长度不定,有效的部分q[start] ~ q[end]。

for(int i=1; i<n; i++){ 
	//start 元素不符合条件,该点已经不在查找的区间内 
	//end 始终不会大于 start+m
	if(i-q[start].y>m){
		start++;
	} 
	printf("%d\n", q[start].x);
	int tmp;
	scanf("%d", &tmp);
	//将 tmp 插入队列 start~end 中 
	while(end>start && q[end-1].x>tmp){
		end--;
	}
	q[end].x=tmp;//覆盖原本q[end]
	q[end].y=i;
	end++;
}

整合代码

#include<stdio.h>
struct node{
	int x;
	int y;
}q[2000010];

int main(){
	int n, m, start=0, end=1;
	scanf("%d%d", &n, &m);
	scanf("%d", &q[0].x);		//先插入队列一个元素 
								//防止队列为空 
	q[0].y=0;
	printf("0\n");	
	for(int i=1; i<n; i++){ 
		if(i-q[start].y>m){
			start++;
		} 
		printf("%d\n", q[start].x);
		int tmp;
		scanf("%d", &tmp);
		while(end>start && q[end-1].x>tmp){
			end--;
		}
		q[end].x=tmp;
		q[end].y=i;
		end++;
	}
	
	return 0;
} 

Problem two:
题目链接
题意:给定一个长度为 n 的数列,进行绕环移动。问所有前缀和都大于等于零的数列有多少。
输入:
每组第一行为 n 代表数列长度,第二行为数列。
当 n 为 0 程序结束。

输出:
每组对应一行 m,代表符合条件的数列个数。

输入样例:
3
2 2 1
3
-1 1 1
1
-1
0
输出样例:
3
2
0

样例说明
第一组:n=3,初始化数列为 2 2 1
可得到三组不同数列 2 2 1 、2 1 2 、1 2 2
每组的前缀和一直都大于0,三组都满足条件,m=3.


思考:如何判断判断该以 a 中第 x 个数为开头的数列前缀和是否都大于等于零?

一般
假如我们知道一个数列的最小前缀和,那么我们只要判断这一个最小前缀和是否大于或等于0。

拓展
假如我们知道以 a 中第 x 个数为开头的数列的最小前缀和,那么我们只要判断这一个最小前缀和是否大于或等于0。

实现
每一个数列开头必须在a1 ~ an-1的范围内,而且长度一定位 n 。
①、我们将数列复制再拼接得到新的数列 a
a1 a2 … an a1 a2…an

②、求出每一位的前缀和得到前缀和数列 sum
sum1 sum2 … sumn sumn+1 sumn+2…sumn+n

③、扫描sum数组,用单调队列维护一个长度为 n 的最小值。在 i >= n的情况下,判断 q[start] - sum[i] 是否大于或等于0


如何理解 q[start] - sum[i] 这操作?
由于每一个 sum[i] 值都是 第 i 项 的累加和。
以 i 为开头的最小前缀和 = 以 1 为开头的最小前缀和 - 以 i 为开头的累加和


实现代码

#include<stdio.h>
#include<memory.h>
int sum[2000020], a[2000020];

struct noed{
	int x;
	int y;
}q[2000020];

int main(){
	int n;
	sum[0]=0;
	
	while(scanf("%d", &n) && n){
		int ans=0;
		for(int i=1; i<=n; i++){
			scanf("%d", &a[i]);
			a[i+n]=a[i];
		}
		
		for(int i=1; i<=2*n; i++){
			sum[i] = sum[i-1] + a[i];
		}
		
		int start=1, end=1;
		q[1].x=sum[1];
		q[1].y=1;
		
		for(int i=1; i<2*n; i++){
			if(i-q[start].y >= n)
				start++;
			//					取不取等号都可
			while(end>start && q[end-1].x>=sum[i]) 
				end--;
			q[end].x=sum[i];
			q[end++].y=i;
			if(i>=n && q[start].x-sum[i-n]>=0)
				ans++;
		}
		
		printf("%d\n", ans);
	}
	
	return 0;
} 

若文章对你有帮助,留下了赞好不啦:)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
deque实现单调队列的关键在于维护队列单调性。在每次插入元素时,需要将队列尾部小于当前元素的元素弹出,以保证队列单调递增性。同时,为了实现O(1)的时间复杂度,可以使用双端队列deque来实现。 具体实现步骤如下: 1. 使用一个deque来保存递增的元素,队首元素即为当前区间的最小值。 2. 当插入一个新元素时,从队列尾部开始,将所有小于等于新元素的元素都弹出,以保持队列递增性。 3. 将新元素插入队列尾部。 4. 当需要获取当前区间的最小值时,直接返回队首元素即可。 这样,通过deque的实现,我们可以实现单调队列,并且在插入、弹出、获取最小值的操作中都能够达到O(1)的时间复杂度。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【模板】deque实现单调队列](https://blog.csdn.net/weixin_30768661/article/details/99426153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [剑指Offer – 面试题59 – II. 队列的最大值(deque模拟单调栈)](https://download.csdn.net/download/weixin_38693192/13752480)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [单调队列-原理详解(deque实现)](https://blog.csdn.net/sinat_40471574/article/details/90577147)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值