题目

世界上如果一定要有土豪,我希望他是我的朋友。

小J和小R今天在学计算机数学,现在老师让所有人都站成了一排,每个人现在都拥有了一个财富值。

小R:"你说相邻的是不是都是我的朋友啊。"

小J:"那我想找找谁是我最富有的朋友。"

小R:"那你可以告诉我每几个人中谁最富有吗?"

小J:"我……我努力……"

现在有 n 个人,站成了一排,请你找到他们中每 k 个人中所拥有的财富值最大是多少。

输入:第一行两个整数 n,k,n 表示总人数,k 表示在多少个人中找最大值。第二行 n 个整数,按顺序表示每个人的财富值。
输出:输出 n−k+1 个整数,依次表示每 k 个人中所拥有的最大财富值。

样例1:

输入

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

输出

3 3 5 5 6 7

样例2:

输入

10 5
1 5 9 4 5 7 1 10 11 13

输出

9 9 9 10 11 13

提示:对于 30% 的数据,保证 n≤100,k≤100。对于 60% 的数据,保证 n≤10000,k≤10000。对于 100% 的数据,保证 n≤1000000,k≤1000000,财富值都在 int 范围内。

思路

用单调递减队列维护滑动窗口的最大值。队列存下标,需要管理两个边界:队头下标若小于 i - k + 1,说明已滑出窗口,弹出;新元素入队前,将队尾所有比它小的元素弹出,保持队列单调递减。这样队头始终是当前窗口的最大值,每次 O(1) 取得。

C++ · p49.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int n,k; // 总人数,k个中找max
    cin>>n>>k;

    vector<int> wealth(n);
    for(int c=0;c<n;c++){
        cin>>wealth[c];
    }

    deque<int> dq;
    for(int i=0;i<n;i++){
        // 弹出窗口外的下标
        while(!dq.empty() && dq.front() <= i-k) dq.pop_front();
        // 弹出所有比当前元素小的元素
        while(!dq.empty() && wealth[dq.back()] < wealth[i]) dq.pop_back();
        dq.push_back(i);

        // 当窗口形成后,输出当前窗口的最大值(队首)
        if(i >= k-1){
            cout<<wealth[dq.front()];
            if(i!=n-1) cout<<' ';
        }
    }
    cout << '\n';
    return 0;
}

复杂度分析

  • 时间复杂度:O(n),每个元素最多入队、出队各一次
  • 空间复杂度:O(k),队列最多存 k 个下标
今日感受

单调队列的想法很巧妙,关键是同时管理好两个边界:队头控制窗口范围,队尾维护单调性。

← 返回菜鸟的coding