简单滑动窗口算法

题目简述

长度为n的数组a,求有多少个存在某元素出现次数 >= m 的连续子区间[l,r]

算法分析

我们可以发现:

若子区间[i,j]不满足情况,固定i不动,j每次后移一步,一次添加一个元素,直到恰好满足 num[a[j]] == m 时,所有以i为起点,以j后面为终点的区间都满足条件,则ans += n-j。

这样我们就记录了所有以 i 为起点的情况,那么while(a[i]!=a[j]),删去头元素i后区间仍然满足条件,即 [i+1,j]也是首次满足条件,同样ans += n-j。

总结起来,我们就是要遍历区间起点 i ,每次找到使区间 [i,j] 第一次满足条件的 j,然后 ans 累加上以 i 为起点的所有情况。即双 for 双指针,时间复杂度 O(n^2)。

所以这里可以用滑动窗口来保存区间信息,使用 while(a[i]!=a[j]) 一次右移多次起点指针,记录多个 i 的情况,这样就只用 单层 for 即可,每个元素进入窗口一次,出窗口一次,时间复杂度 O(n)

C++代码

此题n和m只有40万,ans却卡long long,浪费一次提交。

#include<iostream>
#include<vector>
#include<algorithm>
#include<unordered_map>
using namespace std;


long long No9_SlidingWindow(int n, int m, const vector<int>& a) {
    long long ans = 0;
    int i = 0;
    unordered_map<int, int> num;
    for (int j = 0; j < n; j++) {   // [i,j]窗口长度为 j-i+1
        if (++num[a[j]] != m)continue;  // 元素j进入滑动窗口
        ans += n - j;   //以i为起点的全部满足区间
        while (a[i] != a[j]) {  //删去头元素仍然满足条件
            num[a[i]]--;
            i++;
            ans += n - j;
        }
        //此时删去头元素就不满足条件了,但以头元素为起点的区间已经加到ans中了
        // 仍然删去头元素,使此时的区间不满足条件,同时使以i为起点的区间没被累加过
        num[a[i]]--;
        i++;
    }
    return ans;
}

int main(){
    int n,m;
    cin>>n>>m;
    vector<int>a(n);
    for(int i=0;i<n;i++)
        cin>>a[i];
    
    cout<<No9_SlidingWindow(n, m, a)<<'\n';
    return 0;
}