题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n、m、k(1≤k≤m≤n≤2×105)和长为n的数组a(1≤a[i]≤106),长为m的数组b(1≤b[i]≤106)。
输出a有多少个长为m的连续子数组sub,满足sub与b的交集大小至少是k。交集可以有重复元素。
输入样例
5
7 4 2
4 1 2 3 4 5 6
1 2 3 4
7 4 3
4 1 2 3 4 5 6
1 2 3 4
7 4 4
4 1 2 3 4 5 6
1 2 3 4
11 5 3
9 9 2 2 10 9 7 6 3 6 3
6 9 7 8 10
4 1 1
4 1 5 6
6
输出样例
4
3
2
4
1
算法
哈希表+滑动窗口
比较容易想到滑动窗口,先初始化哈希表window和counter,前者存a数组前m个元素的频数信息,后者存b数组所有元素的频数信息。对于第一个窗口,先计算两者的交集大小s=Σxmin(window[x],counter[x]),其中x是window和counter中的公共元素。
随后滑动窗口来更新s和window,遍历r∈[m+1,n],window[a[r]]会自增,如果自增后满足window[a[r]]≤counter[a[r]],说明多了一个公共元素,自增s。否则增加一个a[r]是多余的,并不会增加公共元素。扩张了右边界还需要收缩一下左边界,需要将a[r−m]移出窗口,令l=r−m,如果window[a[l]]自减之后<counter[a[l]],说明少了一个公共元素,s自减。否则减不减少a[l]都能够覆盖住b中的所有数值a[l],并不会减少公共元素。
复杂度分析
时间复杂度
初始化第一个窗口需要遍历a的前m个元素,b的全体元素,时间复杂度为O(m)。后续的滑动窗口每次改变区间左右端点时更新窗口哈希表的时间复杂度为O(1),滑完整个a数组的时间复杂度为O(n)。因此,整个算法的时间复杂度为O(m+n)。
空间复杂度
空间消耗的瓶颈在于两个哈希表window和counter,在最差情况下这两个哈希表中最多会存储m个键值对,也就是说b中所有元素和a中所有元素都各不相同。此时的额外空间复杂度为O(m),这也是整个算法的额外空间复杂度。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m, k, a[N], b[N];
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d%d", &n, &m, &k);
unordered_map<int, int, custom_hash> window, counter;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(i <= m) window[a[i]]++;
}
for(int i = 1; i <= m; i++) {
scanf("%d", &b[i]);
counter[b[i]]++;
}
int s = 0, ans = 0;
for(auto&[num, freq]: window) {
s += min(freq, counter[num]);
}
if(s >= k) {
ans++;
}
for(int r = m + 1; r <= n; r++) {
int l = r - m;
window[a[r]]++;
if(window[a[r]] <= counter[a[r]]) {
s++;
}
window[a[l]]--;
if(window[a[l]] < counter[a[l]]) {
s--;
}
if(s >= k) {
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}