洛谷P1886 滑动窗口 /【模板】单调队列-队列

P1886 滑动窗口 /【模板】单调队列

题目描述

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

例如,对于序列[1,3,−1,−3,5,3,6,7][1,3,-1,-3,5,3,6,7][1,3,1,3,5,3,6,7]以及k=3k = 3k=3,有如下过程:

窗口位置最小值最大值[1   3  -1] -3   5   3   6   7 −13 1  [3  -1  -3]  5   3   6   7 −33 1   3 [-1  -3   5]  3   6   7 −35 1   3  -1 [-3   5   3]  6   7 −35 1   3  -1  -3  [5   3   6]  7 36 1   3  -1  -3   5  [3   6   7]37\def\arraystretch{1.2} \begin{array}{|c|c|c|}\hline \textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline \verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline \verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline \verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline \verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline \end{array}窗口位置[1   3  -1] -3   5   3   6   7  1  [3  -1  -3]  5   3   6   7  1   3 [-1  -3   5]  3   6   7  1   3  -1 [-3   5   3]  6   7  1   3  -1  -3  [5   3   6]  7  1   3  -1  -3   5  [3   6   7]最小值133333最大值335567

输入格式

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

输出格式

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

输入输出样例 #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%50\%50%的数据,1≤n≤1051 \le n \le 10^51n105
对于100%100\%100%的数据,1≤k≤n≤1061\le k \le n \le 10^61kn106ai∈[−231,231)a_i \in [-2^{31},2^{31})ai[231,231)

解题思路

本题需要高效地计算滑动窗口中的最小值和最大值。由于数据规模较大(n ≤ 10^6),暴力解法会超时,因此需要使用单调队列这一数据结构来优化。

核心思想:单调队列
  1. 最小值计算:维护一个单调递增的双端队列(队头到队尾递增)
    • 当新元素小于队尾元素时,不断弹出队尾(保证队列单调性)
    • 队头元素即为当前窗口的最小值
    • 检查队头元素是否已离开窗口,及时弹出
  2. 最大值计算:维护一个单调递减的双端队列(队头到队尾递减)
    • 当新元素大于队尾元素时,不断弹出队尾(保证队列单调性)
    • 队头元素即为当前窗口的最大值
    • 检查队头元素是否已离开窗口,及时弹出
时间复杂度:O(n)

每个元素最多入队和出队各一次,因此整体时间复杂度为 O(n),满足题目要求。

C++代码实现

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    
    deque<int> min_q; // 单调递增队列(求最小值)
    deque<int> max_q; // 单调递减队列(求最大值)
    vector<int> min_ans, max_ans;
    
    // 计算滑动窗口最小值
    for (int i = 0; i < n; i++) {
        // 移除超出窗口的元素(队头)
        if (!min_q.empty() && min_q.front() <= i - k) {
            min_q.pop_front();
        }
        
        // 维护单调递增特性(从队尾移除大于当前元素的元素)
        while (!min_q.empty() && a[min_q.back()] >= a[i]) {
            min_q.pop_back();
        }
        min_q.push_back(i);
        
        // 当窗口形成时记录最小值
        if (i >= k - 1) {
            min_ans.push_back(a[min_q.front()]);
        }
    }
    
    // 计算滑动窗口最大值
    for (int i = 0; i < n; i++) {
        // 移除超出窗口的元素(队头)
        if (!max_q.empty() && max_q.front() <= i - k) {
            max_q.pop_front();
        }
        
        // 维护单调递减特性(从队尾移除小于当前元素的元素)
        while (!max_q.empty() && a[max_q.back()] <= a[i]) {
            max_q.pop_back();
        }
        max_q.push_back(i);
        
        // 当窗口形成时记录最大值
        if (i >= k - 1) {
            max_ans.push_back(a[max_q.front()]);
        }
    }
    
    // 输出结果
    for (int i = 0; i < min_ans.size(); i++) {
        cout << min_ans[i] << " ";
    }
    cout << "\n";
    for (int i = 0; i < max_ans.size(); i++) {
        cout << max_ans[i] << " ";
    }
    
    return 0;
}

代码说明

  1. 输入处理
    • 使用 ios::sync_with_stdio(false)cin.tie(0) 加速输入
    • 读取序列长度 n 和窗口大小 k
    • 读取序列元素到数组 a
  2. 最小值计算
    • 使用双端队列 min_q 维护单调递增序列
    • 遍历序列时:
      • 检查队头元素是否超出窗口(i - k
      • 从队尾移除比当前元素大的元素,保持队列单调递增
      • 当前元素索引入队
      • 当窗口形成(i ≥ k-1)时,队头元素即为最小值
  3. 最大值计算
    • 使用双端队列 max_q 维护单调递减序列
    • 遍历序列时:
      • 检查队头元素是否超出窗口(i - k
      • 从队尾移除比当前元素小的元素,保持队列单调递减
      • 当前元素索引入队
      • 当窗口形成(i ≥ k-1)时,队头元素即为最大值
  4. 输出结果
    • 先输出所有窗口的最小值序列
    • 再输出所有窗口的最大值序列

复杂度分析

  • 时间复杂度:O(n),每个元素最多入队和出队各一次
  • 空间复杂度:O(n),队列和结果数组最多存储 n 个元素
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值