
2026-06-29:统计包含 K 个不同整数的子数组。用go语言,给定一个整数数组 nums,以及两个整数 k 和 m。你需要统计数组中连续的非空子数组有多少个。
对任意一个子数组,如果它满足:
这个子数组里一共有恰好 k 个不同的数;
1.对于这 k 个不同的数中的每一个,它在该子数组中出现的次数都不少于 m;
2.那么这个子数组就算作满足条件的一个。
最终返回满足上述要求的子数组总数。
1 <= nums.length <= 100000。
1 <= nums[i] <= 100000。
1 <= k, m <= nums.length。
输入: nums = [1,2,1,2,2], k = 2, m = 2。
输出: 2。
解释:
满足条件的子数组为:
子数组 | 不同整数 | 频率 |
|---|---|---|
[1, 2, 1, 2] | {1, 2} → 2 | {1: 2, 2: 2} |
[1, 2, 1, 2, 2] | {1, 2} → 2 | {1: 2, 2: 3} |
因此,答案是 2。
题目来自力扣3859。
题目要求统计恰好拥有k种不同数字,且这k种数字每一种在子数组内出现次数都≥m的连续子数组数量。
直接求「恰好k个」很难滑动窗口一次性算出,因此采用经典容斥差分思想:
满足恰好k种的合法子数组总数 = calc(k) − calc(k+1)
其中函数 calc(t) 的定义:统计不同数字种类 ≤ t,且窗口内≥m次的数字总数≥k 的所有子数组数量。
calc(k):所有不同数≤k、且有至少k种数字出现≥m次的子数组;calc(k+1):所有不同数≤k+1、且有至少k种数字出现≥m次的子数组;
两者相减后,剩下的就是不同数字严格等于k种、且这k种全部出现≥m次的目标子数组。calc(t) 内部使用不定长滑动窗口(双指针left、right),right循环逐个取数组元素作为窗口右边界,left只向右移动不回退,全程维护3个核心变量:
cnt 哈希map:记录当前窗口 [left, right] 内每个数字的出现次数;geM:当前窗口内出现次数 ≥ m 的数字总个数;left:窗口左边界下标,所有以当前right为右端点、满足约束的合法子数组左起点都在 [0, left-1],因此每次累加left到答案。geM 计数 +1;约束条件(需要持续收缩窗口的触发条件):
窗口不同数字总数 len(cnt) ≥ t 并且 geM ≥ k
只要同时满足上面两条,就不断把最左侧元素nums[left]移出窗口:
geM 计数 -1;收缩完成后,窗口 [left, right] 是以right为右端点、满足 len(cnt)<t 或 geM<k 的最小左边界窗口。
所有起点在 0 ~ left-1 的子数组 [s, right](s < left),全部满足 len(cnt) ≤ t 且 geM ≥ k,都是calc(t)要统计的合法子数组。
以当前right为右端点的合法子数组一共有 left 个(起点0、1……left-1,共left个),执行 ans += int64(left)。
遍历完数组所有右边界后,ans即为全部「不同数字≤t、且至少k种数字出现≥m次」的子数组总数。
最终答案 = calc(2) − calc(3)
数组下标0~4:[1,2,1,2,2],t=2,k=2,m=2 初始化 cnt空map、geM=0、left=0、ans=0
数组只有1、2两种数字,len(cnt)永远≤2 <3,全程不会触发窗口收缩逻辑,left始终固定为0 每一轮 ans += 0,遍历完成后 calc(3)=0
calc(2) − calc(3) = 2 − 0 = 2,与题目输出一致。
仅哈希表cnt占用额外空间,哈希表存储窗口内不重复数字; 最坏情况数组所有数字互不相同,哈希表存储全部n个数字; 额外空间复杂度 O(n)。
.
package main
import (
"fmt"
)
func countSubarrays(nums []int, k, m int)int64 {
calc := func(distinctLimit int) (ans int64) {
cnt := map[int]int{}
geM := 0// 窗口中的出现次数 >= m 的元素个数
left := 0
for _, x := range nums {
// 1. 入
cnt[x]++
if cnt[x] == m {
geM++
}
// 2. 出
forlen(cnt) >= distinctLimit && geM >= k {
out := nums[left]
if cnt[out] == m {
geM--
}
cnt[out]--
if cnt[out] == 0 {
delete(cnt, out)
}
left++
}
// 3. 更新答案
ans += int64(left)
}
return
}
return calc(k) - calc(k+1)
}
func main() {
nums := []int{1, 2, 1, 2, 2}
k := 2
m := 2
result := countSubarrays(nums, k, m)
fmt.Println(result)
}

.
# -*-coding:utf-8-*-
from typing import List
def countSubarrays(nums: List[int], k: int, m: int) -> int:
# 内部函数:统计满足 不同元素个数 >= distinctLimit 且
# 出现次数 >= m 的元素个数 >= k 的子数组个数
def calc(distinctLimit: int) -> int:
cnt = {} # 当前窗口中各元素的出现次数
geM = 0 # 出现次数 >= m 的元素个数
left = 0
ans = 0
for x in nums:
# 1. 将右端点元素加入窗口
cnt[x] = cnt.get(x, 0) + 1
if cnt[x] == m:
geM += 1
# 2. 当窗口满足条件时,收缩左边界直到条件不再满足
while len(cnt) >= distinctLimit and geM >= k:
out = nums[left]
if cnt[out] == m:
geM -= 1
cnt[out] -= 1
if cnt[out] == 0:
del cnt[out]
left += 1
# 3. 对于当前右端点,所有左端点 < left 的子数组均满足条件
ans += left
return ans
# 容斥:不同元素个数恰好为 k,且每个元素的出现次数都 >= m
return calc(k) - calc(k + 1)
if __name__ == "__main__":
nums = [1, 2, 1, 2, 2]
k = 2
m = 2
result = countSubarrays(nums, k, m)
print(result) 
.
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
long long countSubarrays(const vector<int>& nums, int k, int m) {
// 内部函数:统计满足 不同元素个数 >= distinctLimit 且
// 出现次数 >= m 的元素个数 >= k 的子数组个数
auto calc = [&](int distinctLimit) -> long long {
unordered_map<int, int> cnt;
int geM = 0; // 出现次数 >= m 的元素个数
int left = 0;
long long ans = 0;
for (int x : nums) {
// 1. 将右端点元素加入窗口
cnt[x]++;
if (cnt[x] == m) {
geM++;
}
// 2. 当窗口满足条件时,收缩左边界直到条件不再满足
while ((int)cnt.size() >= distinctLimit && geM >= k) {
int out = nums[left];
if (cnt[out] == m) {
geM--;
}
cnt[out]--;
if (cnt[out] == 0) {
cnt.erase(out);
}
left++;
}
// 3. 对于当前右端点,所有左端点 < left 的子数组均满足条件
ans += left;
}
return ans;
};
// 容斥:不同元素个数恰好为 k,且每个元素的出现次数都 >= m
return calc(k) - calc(k + 1);
}
int main() {
vector<int> nums = {1, 2, 1, 2, 2};
int k = 2, m = 2;
long long result = countSubarrays(nums, k, m);
cout << result << endl;
return0;
}
