[哈希表, 滑动窗口] 统计好子数组的数目

题目

2537. 统计好子数组的数目

给你一个数组 \(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)\) 的时间内获取子数组的信息呢?

答案是滑动窗口

方法一:哈希表 + 滑动窗口

思路

  1. 滑动窗口要满足窗口维护的内容有单调性
    • 不难想到,右侧加入元素 能够使得窗口中的 \(pairs\) 不变或增加;
    • 同时,左侧减少元素 能够使得窗口中的 \(pairs\) 不变或减少;

  1. 如何计算满足条件的子数组数目?

观察官方用例:

  • 输入:\(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)\)

参考题型

posted @ 2025-04-16 23:14  kris4js  阅读(29)  评论(0)    收藏  举报