算法--單調隊列

單調隊列

定義:

單調隊列就是具有單調性的隊列。分爲:單調遞增和單調遞減兩種。因爲其具有單調性所有,單調隊列可以快速的找出當前隊列中最大或最小的元素。使用的頻率不高。

使用方法:

遍歷數組中的每一個元素,如果該元素的值大於(小於)當前隊尾元素的值進隊;否則,將隊尾元素彈出,再次比較,知道該元素的值大於當前隊尾元素或隊列爲空。

核心代碼

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章