[哈希表, 滑动窗口] 统计好子数组的数目
题目
给你一个数组 \(a\) 和一个整数 \(k\),返回 \(a\) 中 好子数组的数目。
定义好子数组:
- 至少有 \(k\) 对下标满足:\(i < j\) 且 \(a[i] = a[j]\)。
注意:子数组是原数组中一段连续非空的元素序列。
- \(1 ≤ n ≤ 1e5\)
- \(1 ≤ a[i], k ≤ 1e9\)
观察数据范围 \(1 ≤ n ≤ 1e5\),那么算法的时间复杂度考虑 \(O(n)\) 或 \(O(nlogn)\)。最近两天做多了枚举,第一反应就是 枚举右,维护左。那么深入思考一下能否枚举吧。
- 考虑枚举 \(a_i\),那么我们维护 \(i\) 左侧的所有元素个数 \(cnt[a_j], j ≤ i\)。
- 观察到:当我们枚举到 \(a_i\) 时,如果 \(cnt[a_i]\) 不为 \(0\),那么相等数对的个数(不妨定义为 \(pairs\))就增加 \(cnt[a_i]\) 个,也就是说枚举 \(a_i\) 能够使得 \(pairs\) 不变或增加。
现在的问题是:我们只有 \([0: i]\) 的信息,无法获取子数组的信息。
- 什么数据结构/算法能够在 \(O(n)\) 的时间内获取子数组的信息呢?
答案是滑动窗口。
方法一:哈希表 + 滑动窗口
思路:
- 滑动窗口要满足窗口维护的内容有单调性。
- 不难想到,右侧加入元素 能够使得窗口中的 \(pairs\) 不变或增加;
- 同时,左侧减少元素 能够使得窗口中的 \(pairs\) 不变或减少;
- 如何计算满足条件的子数组数目?
观察官方用例:
- 输入:\(A\) = \([3, 1, 4, 3, 2, 2, 4]\), \(k = 2\).
- 输出:\(4\)
- 解释:总共有 \(4\) 个不同的好子数组。
(1)\([3,1,4,3,2,2]\) 的 \(pairs\) 为 \(2\),此时 \(left = 0, i = 5\);
(2)\([3,1,4,3,2,2,4]\) 的 \(pairs\) 为 \(3\),此时 \(left=0, i = 6\);
(3)\([1,4,3,2,2,4]\) 的 \(pairs\) 为 \(2\),此时 \(left=1, i=6\);
(4)\([4,3,2,2, 4]\) 的 \(pairs\) 为 \(2\),此时 \(left=2, i = 6\);
对于 \((2, 3, 4)\) 来说,我们能够发现这三个子数组有相同的后缀 \([4,3,2,2,4]\)。恰好 \([4,3,2,2,4]\) 是最后一个满足 \(pairs ≥ k\) 的窗口子数组。
通用地认为,\([left, right]\) 是枚举 \(right\) 为窗口右侧的情况下最后一个满足 \(pairs ≥ k\) 的窗口。
-
如果 \(left\) 再向右移动一位,那么 \([left, right]\) 就不满足条件了。
-
但是,\([left - 1, right]\) 能够满足条件,更不必说 \([0:left-1, right]\) 都能满足条件。
-
也就是说,当右端点固定在 \(right\) 时,左端点在 \(0,1,2,…,left−1\) 的所有子数组都是满足要求的,这一共有 \(left\) 个子数组。
代码
long long countGood(vector<int> &a, int k) {
ll ans = 0;
unordered_map<int, int> cnt;
int pairs = 0, left = 0;
for (int x : a) {
// 当 x 进入, 会增加 cnt[x] 个相等数对
pairs += cnt[x]++;
while (pairs >= k) {
// 当 a[left] 退出, 会减少 cnt[a[left]] - 1 个相等数对
cnt[a[left]]--;
pairs -= cnt[a[left]];
left++;
}
ans += left;
}
return ans;
}
复杂度
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(n)\)
参考题型
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 「§2.3.1 越长越合法」

浙公网安备 33010602011771号