算法--单调队列

单调队列

定义:

单调队列就是具有单调性的队列。分为:单调递增和单调递减两种。因为其具有单调性所有,单调队列可以快速的找出当前队列中最大或最小的元素。使用的频率不高。

使用方法:

遍历数组中的每一个元素,如果该元素的值大于(小于)当前队尾元素的值进队;否则,将队尾元素弹出,再次比较,知道该元素的值大于当前队尾元素或队列为空。

核心代码

deque<int> q;
//构造一个单调递增队列
for(int i=0;i<n;++i)
{
    //如果队列不为空且当前元素的值小于等于队列中的最后一个元素
    while((!q.empty())&&arr[i]<=arr[q.back()])
    {
        q.pop_back();
    }
    q.push_back();
}

列题1:滑动窗口(acwing154)

题目描述:有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。您只能在窗口中看到k个数字。要求输出滑动窗口中的最大值和最小值。每次滑动窗口向右移动一个位置。

题目分析:

给定数组的大小为n,窗口大小为k,每次从元素中长度为k的元素中选取一个最大值和最小值。如果使用暴力算法,实践复杂度为(n-k)(k)。遍历一遍数组的时间复杂度为(n-k),在长度为k的数组中寻找最大小值的时间复杂度为k。如果我们有办法减少寻找最大最小值的事件复杂度那么算法的时间复杂将会降低。这就可以使用单调队列。构造一个长度为k的单调队列,这样就可以快速确定窗口中的最大值和最小值。

代码:

#include <iostream>
#include <deque>
#include <vector>
using namespace std;
int main()
{
    int n,k;
    cin>>n>>k;
    vector<int> arr(n);
    
    for(int i=0;i<n;++i)
    {
        cin>>arr[i];
    }
    deque<int> q;
    //构造一个长度为k的单调递增队列(求最小值)
    for(int i=0;i<n;++i)
    {
        //如果i大于等于k,说明窗口中的元素个数等于k
        if(i>=k)
        {
            cout<<arr[q.front()]<<" ";
        }
        //当i-q.front()==k时,说明窗口中的元素个数等于k
        if(!q.empty()&&i-q.front()==k)
        {
            //将队列中第一个元素弹出
            q.pop_front();
        }
        //如果队列不为空且队列最后一个元素的值大于等于arr[i]
        while(!q.empty()&&arr[q.back()]>=arr[i])
        {
            //将队列中最后一个元素弹出
            q.pop_back();
        }
        //将该元素的下标入队
        q.push_back(i);
    }
    cout<<arr[q.front()]<<endl;
    q.clear();
    for(int i=0;i<n;++i)
    {
        if(i>=k)
        {
            cout<<arr[q.front()]<<" ";
        }
        if(!q.empty()&&i-q.front()==k)
        {
            q.pop_front();
        }
        while(!q.empty()&&arr[q.back()]<=arr[i])
        {
            q.pop_back();
        }
        q.push_back(i);
    }
    cout<<arr[q.front()];
    return 0;
}

列题2:最大子序和(acwing135)

题目描述:输入一个长度为n的整数序列,从中找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大。

题目分析:

题目要求计算长度不超过m的连续子序列的最大和,为了避免每次都求和计算,这里使用前缀和来简化计算,通过前缀和可以快速求出连续子序列的和。我们定义一个长度为m+1的单调递增队列,这样我们就可以快速找出长度为m+1的元素中最小的一个,得到该值之后我们就可以快速的求出i左侧m范围内的最大连续子序列的和。这样遍历数组一遍就可以求出长度不超过m的连续的子序列的和。

代码:

#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
int main()
{
    int n, m;
    cin >> n >> m;
    vector<int> arr(n);
    cin >> arr[0];
    for (int i = 1; i < n; ++i)
    {
        cin >> arr[i];
        //将数组设置为前缀和数组
        arr[i] += arr[i - 1];
    }
    deque<int> q;
    int maxNum = arr[0];
    q.push_back(0);
    for (int i = 1; i < n; ++i)
    {
        //前缀和:s[j]-s[k]==a[k+1]+a[k+2]+...a[j],所以单调队列的大小为m+1
        if (!q.empty() && i - q.front() == m + 1)
        {
            q.pop_front();
        }
        //构造单调递增队列
        while ((!q.empty()) && arr[i] <= arr[q.back()])
        {
            q.pop_back();
        }
        q.push_back(i);
        maxNum = max(maxNum, arr[i] - arr[q.front()]);
    }
    cout << max(maxNum, arr[n - 1] - arr[q.front()]) << endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章