单调队列-C语言

单调队列是一种特殊的队列数据结构,它能够在队列中维护元素的单调性。单调队列通常用于解决一些需要维护滑动窗口内最大值或最小值的问题,它能够在O(1)的时间复杂度内实现队首和队尾的插入和删除操作。

使用单调队列可以有效地解决一些与滑动窗口相关的问题,如找到滑动窗口中的最大值或最小值。这种数据结构在解决一些算法问题时非常有用,例如动态规划中的优化问题、数组操作中的滑动窗口问题等。

例题:

题目描述
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如,对于序列 [1,3,-1,-3,5,3,6,7] 以及 k = 3,有如下过程:

{窗口位置} {最小值} {最大值} 
[1   3  -1] -3   5   3   6   7   -1  3 
 1  [3  -1  -3]  5   3   6   7   -3  3 
 1   3 [-1  -3   5]  3   6   7   -3  5 
 1   3  -1 [-3   5   3]  6   7   -3  5 
 1   3  -1  -3  [5   3   6]  7    3  6 
 1   3  -1  -3   5  [3   6   7]   3  7 

输入格式
输入一共有两行,第一行有两个正整数 n,k。
第二行 n 个整数,表示序列 a
输出格式
输出共两行,第一行为每次窗口滑动的最小值   
第二行为每次窗口滑动的最大值
样例 #1
样例输入 #1
8 3
1 3 -1 -3 5 3 6 7
样例输出 #1
-1 -3 -3 -3 3 3
3 3 5 5 6 7
提示
【数据范围】    
对于 50% 的数据,1 <= n <= 10^5;  
对于 100% 的数据,1<= k <= n <= 10^6,a_i  [-2^31,2^31)。

这是一个非常经典的单调队列解决滑动窗口问题。我们只需要用两个单调队列分别解决最大值和最小值就行了。

看完题目后,我们只需要在滑动窗口时,队头超出窗口时出队,将当前元素与窗尾部元素比较,进行出队,这是一个队头跟队尾都出队的情况,所以要弄明白这里面的过程。

每次窗口滑动的最小值:

队头超出窗口时出队

当前元素与窗尾部元素比较时,将所有比当前元素大的元素从尾部移除。

第一次,队列中放{1};

第二次,队列中放{1,3};

第三次,队列中1和3比-1大,从尾部出列,标记到-1,次数到达3次,输出当前元素-1;

第四次,-1大于-3,从尾部出列,输出当前元素-3;

第五次,队列中放{-3,5},输出当前元素-3;

第六次,5大于3,从尾部出列,队列中放{-3,3},输出当前元素-3;

第七次,队头超出窗口,-3出队,队列中放{3,6},输出当前元素3;

第八次,队列中放{3,6,7},输出当前元素3;

代码如下:

void min()
{
	int head = 1,tail = 0;
	for(int i=1; i<=n; i++) {
		//队头超出窗口出队
		while(head <= tail && b1[head] + k <=i) head++;
		//然后将当前元素与窗口尾部元素比较,将所有比当前元素大的元素从尾部移除	
		while(head <= tail && a[i] < a[b1[tail]]) tail--;
		//最后将当前元素插入尾部
		b1[++tail] = i;
		if(i>=k) {
			printf("%d ",a[b1[head]]);
		}
	}
	printf("\n");
}

每次窗口滑动的最大值:

跟每次窗口滑动的最小值类似,不同点为当前元素与窗尾部元素比较时,将所有比当前元素小的元素从尾部移除。

void max()
{
	int head = 1,tail = 0;
	for(int i=1; i<=n; i++) {
		//队头超出窗口出队
		while(head <= tail && b2[head] + k <=i) head++;
		//然后将当前元素与窗口尾部元素比较,将所有比当前元素小的元素从尾部移除
		while(head <= tail && a[i] > a[b2[tail]]) tail--;
		//最后将当前元素插入尾部			
		b2[++tail] = i;
		if(i>=k) {
			printf("%d ",a[b2[head]]);
		}
 	}
 	printf("\n");
}

完整代码如下:

#include <stdio.h>
#define N 1000005
int a[N],b1[N],b2[N],n,k;

void min()
{
	int head = 1,tail = 0;
	for(int i=1; i<=n; i++) {
		//队头超出窗口出队
		while(head <= tail && b1[head] + k <=i) head++;
		//然后将当前元素与窗口尾部元素比较,将所有比当前元素大的元素从尾部移除	
		while(head <= tail && a[i] < a[b1[tail]]) tail--;
		//最后将当前元素插入尾部
		b1[++tail] = i;
		if(i>=k) {
			printf("%d ",a[b1[head]]);
		}
	}
	printf("\n");
}
void max()
{
	int head = 1,tail = 0;
	for(int i=1; i<=n; i++) {
		//队头超出窗口出队
		while(head <= tail && b2[head] + k <=i) head++;
		//然后将当前元素与窗口尾部元素比较,将所有比当前元素小的元素从尾部移除
		while(head <= tail && a[i] > a[b2[tail]]) tail--;
		//最后将当前元素插入尾部			
		b2[++tail] = i;
		if(i>=k) {
			printf("%d ",a[b2[head]]);
		}
 	}
 	printf("\n");
}
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1; i<=n; i++) {
		scanf("%d",&a[i]);
	}
	min();
	max();	
	return 0;
}

求求您珍贵的双手给博主点点赞

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值