[数据结构]--单调队列

文章介绍了单调队列的概念,它是具有单调性的双端队列,常用于解决动态小区间内的极值问题。通过一个模拟示例展示了单调递减队列的操作过程。接着,文章提供了两个例题,一是利用单调队列解决滑动窗口问题,求解每次窗口滑动后的最大值和最小值;二是寻找绝对差不超过限制的最长连续子数组,同样利用了单调队列来维护最大值和最小值,从而确定满足条件的子数组长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.单调队列介绍

单调队列:队列元素之间的关系具有单调性(从队首到队尾单调递增/递减),队首和队尾都可以进行入队出队(即插入删除)操作,本质是由双端队列deque实现

2.适用问题

通常解决动态小区间中寻找极值问题。

3.模拟单调递减队列(类比单调栈

依次加入7,8,6,2,5,五次操作后队内元素排列情况:

1:7

2:8

3:8  6

4:8  6  2

5:8  6  5

1:队内为空,7入队

2:第二个元素 8  >  7 ,7出队 ,8入队

3:第三个元素 6  < 8 ,  6入队

4:第四个元素 2  < 6,    2入队

5:第五个元素 5  > 2,    2出队,  5 < 6,  5入队

4.例题

1.滑动窗口(滑动窗口 /【模板】单调队列 - 洛谷)

有一个长为 n的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

The array is [1,3,-1,-3,5,3,6,7], and k = 3。

输入格式

输入一共有两行,第一行有两个正整数 n,k。 第二行 n 个整数,表示序列 a

输出格式

输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值

输入输出样例

输入 

8 3
1 3 -1 -3 5 3 6 7

输出

-1 -3 -3 -3 3 3
3 3 5 5 6 7

说明/提示

【数据范围】
对于 50% 的数据,1≤n≤10^5;
对于 100% 的数据,1≤k≤n≤10^6,ai​∈[−2^31,2^31)。

 AC代码:

#include <iostream>
#include <algorithm>
#include <deque>

using namespace std;

const int N = 1e6 + 10;

int a[N];
deque<int> q;		//存下标 
 
int main()
{
	int n, k;
	cin >> n >> k;
	for(int i = 0; i < n; i++ )
	cin >> a[i];
	for(int i = 0; i < n; i++ )		//最小值(单调递增队列(队首即为最小值)) 
	{
		while(!q.empty() && i - k + 1 > q.front() )		//队头滑出窗口
		q.pop_front();
		while(!q.empty() && a[i] < a[q.back()] )		//去除尾部所有比新元素大的 
		q.pop_back(); 
		q.push_back(i);			//新元素入队 
		if(i + 1 >= k)
		{ 
			cout << a[q.front()] << " ";		//输出队首 	
		}
	}
	cout << endl;
	q.clear();			//清空
	for(int i = 0; i < n; i++ )		//最大值(单调递减队列(队首即为最大值))
	{ 
		while(!q.empty() && i - k + 1 > q.front() )		//队首滑出窗口 
		q.pop_front();
		while(!q.empty() && a[i] > a[q.back()] )		//新元素比队尾元素大 
		q.pop_back();							
		q.push_back(i);				//新元素入队 
		if(i + 1 >= k)
		{
			cout << a[q.front()] << " ";
		}
	}
	cout << endl;
	return 0;
}

2.绝对差不超过限制的最长连续子数组

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。

如果不存在满足条件的子数组,则返回 0 。

示例 1:

输入:nums = [8,2,4,7], limit = 4
输出:2 
解释:所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4. 
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4. 
因此,满足题意的最长子数组的长度为 2 。

示例 2:

输入:nums = [10,1,2,4,7,2], limit = 5
输出:4 
解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。

示例 3:

输入:nums = [4,2,2,2,4,4,2,2], limit = 0
输出:3

提示:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
0 <= limit <= 10^9

题解:滑动窗口 + 单调队列

在代码中,使用两个单调队列,一个单调递增队列 qmin 维护最小值,一个单调递减队列 qmax 维护最大值。这样我们只需要计算两个队列的队首的差值,即可知道当前窗口是否满足条件。

AC代码:

class Solution {
public:
    int longestSubarray(vector<int>& nums, int limit) {
        deque<int> qmin, qmax;          //两个单调队列,维护最大值最小值
        
        int l = 0, r = 0;            //下标
        int ans = 0;                //保存最长连续子数组长度
        
        while(r < nums.size() )
        {
            while(!qmin.empty() && qmin.back() > nums[r] )    //单调递增队列
            qmin.pop_back();
            while(!qmax.empty() && qmax.back() < nums[r] )    //单调递减队列
            qmax.pop_back();

            qmin.push_back(nums[r]);
            qmax.push_back(nums[r]);
            
            while(!qmin.empty() && !qmax.empty() && qmax.front() - qmin.front() > limit )
            {
                if(nums[l] == qmax.front() )
                qmax.pop_front();
            
                if(nums[l] == qmin.front() )
                qmin.pop_front();
                
                l ++;
            }
            ans = max(ans, r - l + 1);    //更新
            r ++;
        }
        return ans;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值