题目链接
题目描述
给定一个长度为 的整数数组
和一个长度为
的整数数组
。一个长度为
的数组如果可以通过重新排列元素,与数组
在对应位置上至少有
个元素相等,则称该数组是“可匹配的”。
任务是,计算数组 中有多少个长度为
的连续子段是“可匹配的”。
解题思路
这个问题的核心是判断一个子段和一个目标数组 在元素构成上的相似度。一个子段“可匹配”,意味着该子段与数组
的元素交集大小至少为
。具体来说,对于每一种数值
,它在交集中的贡献是
。所有数值贡献的总和就是匹配度。
由于我们需要对 的所有长度为
的连续子段进行判断,这自然引出了滑动窗口的解法。我们可以维护一个长度为
的窗口,在
上从左到右滑动,并高效地更新窗口内的元素频率以及与
的匹配度。
算法步骤如下:
- 预计算:首先,使用哈希表(
)计算出数组
中每个元素的频率,记为
。
- 初始化窗口:
- 创建一个哈希表
来维护当前窗口内元素的频率。
- 处理
的前
个元素,填充
。
- 计算初始窗口的匹配度
。这个值等于
。
- 如果
,则答案计数器加一。
- 创建一个哈希表
- 滑动窗口:
- 从第
个元素开始,向右滑动窗口直到数组末尾。在每次滑动中:
- 移除左边界元素:设要移除的元素为
。在更新
之前,检查它是否对匹配度有贡献。如果
,说明移除的这个元素是一个有效的匹配元素,因此将
减一。然后,更新
。
- 添加右边界元素:设新加入的元素为
。先更新
。更新后,检查它是否能形成新的匹配。如果
,说明新加入的元素是一个有效的匹配元素,因此将
加一。
- 检查并更新答案:在每次滑动后,检查当前的
是否不小于
。如果是,则答案计数器加一。
- 从第
- 多组测试:题目要求处理多组测试用例,因此需要将上述逻辑封装起来,循环调用。
通过这种方式,每次窗口滑动时,我们都只对进出窗口的两个元素进行 的更新(哈希表操作均摊为
),从而将每个测试用例的总时间复杂度优化到
。
代码
#include <iostream>
#include <vector>
#include <map>
using namespace std;
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<int> a(n);
map<int, int> freq_b;
for (int i = 0; i < n; ++i) cin >> a[i];
for (int i = 0; i < m; ++i) {
int val;
cin >> val;
freq_b[val]++;
}
map<int, int> freq_window;
int match_count = 0;
int ans = 0;
// 初始化第一个窗口
for (int i = 0; i < m; ++i) {
if (freq_window[a[i]] < freq_b[a[i]]) {
match_count++;
}
freq_window[a[i]]++;
}
if (match_count >= k) {
ans++;
}
// 滑动窗口
for (int i = m; i < n; ++i) {
// 移除左边界元素
int left_val = a[i - m];
if (freq_window[left_val] <= freq_b[left_val]) {
match_count--;
}
freq_window[left_val]--;
// 添加右边界元素
int right_val = a[i];
if (freq_window[right_val] < freq_b[right_val]) {
match_count++;
}
freq_window[right_val]++;
if (match_count >= k) {
ans++;
}
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
solve(sc);
}
}
private static void solve(Scanner sc) {
int n = sc.nextInt();
int m = sc.nextInt();
int k = sc.nextInt();
int[] a = new int[n];
Map<Integer, Integer> freqB = new HashMap<>();
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
for (int i = 0; i < m; i++) {
int val = sc.nextInt();
freqB.put(val, freqB.getOrDefault(val, 0) + 1);
}
Map<Integer, Integer> freqWindow = new HashMap<>();
int matchCount = 0;
int ans = 0;
// 初始化第一个窗口
for (int i = 0; i < m; i++) {
int currentFreq = freqWindow.getOrDefault(a[i], 0);
int targetFreq = freqB.getOrDefault(a[i], 0);
if (currentFreq < targetFreq) {
matchCount++;
}
freqWindow.put(a[i], currentFreq + 1);
}
if (matchCount >= k) {
ans++;
}
// 滑动窗口
for (int i = m; i < n; i++) {
// 移除左边界元素
int leftVal = a[i - m];
int currentFreqLeft = freqWindow.get(leftVal);
int targetFreqLeft = freqB.getOrDefault(leftVal, 0);
if (currentFreqLeft <= targetFreqLeft) {
matchCount--;
}
freqWindow.put(leftVal, currentFreqLeft - 1);
// 添加右边界元素
int rightVal = a[i];
int currentFreqRight = freqWindow.getOrDefault(rightVal, 0);
int targetFreqRight = freqB.getOrDefault(rightVal, 0);
if (currentFreqRight < targetFreqRight) {
matchCount++;
}
freqWindow.put(rightVal, currentFreqRight + 1);
if (matchCount >= k) {
ans++;
}
}
System.out.println(ans);
}
}
import sys
from collections import defaultdict
def solve():
n, m, k = map(int, sys.stdin.readline().split())
a = list(map(int, sys.stdin.readline().split()))
b = list(map(int, sys.stdin.readline().split()))
freq_b = defaultdict(int)
for val in b:
freq_b[val] += 1
freq_window = defaultdict(int)
match_count = 0
ans = 0
# 初始化第一个窗口
for i in range(m):
if freq_window[a[i]] < freq_b[a[i]]:
match_count += 1
freq_window[a[i]] += 1
if match_count >= k:
ans += 1
# 滑动窗口
for i in range(m, n):
# 移除左边界元素
left_val = a[i - m]
if freq_window[left_val] <= freq_b[left_val]:
match_count -= 1
freq_window[left_val] -= 1
# 添加右边界元素
right_val = a[i]
if freq_window[right_val] < freq_b[right_val]:
match_count += 1
freq_window[right_val] += 1
if match_count >= k:
ans += 1
print(ans)
def main():
t = int(sys.stdin.readline())
for _ in range(t):
solve()
if __name__ == "__main__":
main()
算法及复杂度
- 算法:滑动窗口 + 哈希表。
- 时间复杂度:
,对于每个测试用例,时间复杂度为
,因为我们只对数组
进行了一次线性扫描。
是所有测试用例的
的总和。
- 空间复杂度:
,主要用于存储数组
和窗口的频率哈希表。