2026-06-29:统计包含 K 个不同整数的子数组。用go语言,给定一个整数数组 nums,以及两个整数 k 和 m。你需要统计数组中连续的非空子数

网易专栏1个月前发布 nxnqh
41 0 0

🤖 AI总结

主题

使用滑动窗口和容斥原理统计满足特定条件的子数组数量

摘要

本文通过滑动窗口与容斥差分,统计恰好有k种不同整数且每种出现≥m次的连续子数组数量,时间复杂度O(n)。

关键信息

  • 1 统计连续子数组,要求恰好包含k种不同整数且每种出现不少于m次
  • 2 采用容斥差分思想,通过calc(k)减去calc(k+1)实现
  • 3 滑动窗口维护三个变量:哈希计数、geM计数、左边界left

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,右边界逐个右移)

    calc(t) 内部使用不定长滑动窗口(双指针left、right),right循环逐个取数组元素作为窗口右边界,left只向右移动不回退,全程维护3个核心变量:

    1.cnt哈希map:记录当前窗口 [left, right] 内每个数字的出现次数;

  • 2.geM:当前窗口内出现次数 ≥ m的数字总个数;

  • 3.left:窗口左边界下标,所有以当前right为右端点、满足约束的合法子数组左起点都在 [0, left-1],因此每次累加left到答案。

    逐轮标准三步流程(每处理一个右边界元素x = nums[right]) 步骤1:将当前右边界元素x加入窗口,更新计数与geM

    1. 哈希表 cnt[x] 计数+1;

  • 2. 特殊判断:如果自增后 cnt[x] 刚好等于 m,说明这个数字第一次达到≥m次,geM 计数 +1

    • 若原本cnt[x]已经大于m,自增后依旧≥m,geM不变化。

    步骤2:收缩左边界left,直到窗口不满足两个约束条件之一

    约束条件(需要持续收缩窗口的触发条件):
    窗口不同数字总数 len(cnt) ≥ t 并且 geM ≥ k
    只要同时满足上面两条,就不断把最左侧元素nums[left]移出窗口:

    1. 取出待移除元素 out = nums[left];

  • 2. 若 cnt[out] 恰好等于 m:移除后该数字出现次数会小于m,因此geM 计数 -1

  • 3. cnt[out] 计数-1;

  • 4. 若减完后 cnt[out] == 0:该数字在窗口内完全消失,从哈希表cnt中删除,窗口不同数字种类数len(cnt)自动减少;

  • 5. left指针向右移动一位,重复判断约束条件,直到不再同时满足「len(cnt)≥t、geM≥k」才停止收缩。

    收缩完成后,窗口 [left, right] 是以right为右端点、满足 len(cnt)
    所有起点在 0 ~ left-1 的子数组 [s, right](s < left),全部满足len(cnt) ≤ t 且 geM ≥ k,都是calc(t)要统计的合法子数组。

    步骤3:累加当前合法子数组数量到ans

    以当前right为右端点的合法子数组一共有 left 个(起点0、1……left-1,共left个),执行ans += int64(left)

    calc(t) 函数返回值

    遍历完数组所有右边界后,ans即为全部「不同数字≤t、且至少k种数字出现≥m次」的子数组总数。

    三、完整样例推演:nums=[1,2,1,2,2], k=2, m=2

    最终答案 = calc(2) − calc(3)

    1. 先计算 calc(2):统计不同数≤2、geM≥2 的子数组总数

    数组下标0~4:[1,2,1,2,2],t=2,k=2,m=2
    初始化 cnt空map、geM=0、left=0、ans=0

    right=0,x=1

    1. 入窗口:cnt[1]=1,不等于2 → geM不变0

  • 2. 收缩判断:len(cnt)=1 <2,不收缩,left保持0

  • 3. ans += 0 → ans=0

    right=1,x=2

    1. 入窗口:cnt[2]=1,geM=0

  • 2. len(cnt)=2≥2,但geM=0<2,不收缩,left=0

  • 3. ans +=0 → ans=0

    right=2,x=1

    1. cnt[1]变为2,恰好等于m=2 → geM=1

  • 2. len(cnt)=2≥2,geM=1<2,不收缩,left=0

  • 3. ans +=0 → ans=0

    right=3,x=2

    1. cnt[2]变为2,等于m=2 → geM=2

  • 2. 触发收缩条件:len(cnt)=2≥2 && geM=2≥2,开始移出左元素out=1(left=0)

    • cnt[out]=2 == m → geM=1

  • • cnt[1]减为1,不为0,不删除map键

  • • left变为1
    再次判断:len(cnt)=2≥2,但geM=1<2,停止收缩

    3. ans += left=1 → ans=1
    含义:以下标3为右端点,起点0的子数组[0,3]满足calc(2)条件,计1个

    right=4,x=2

    1. cnt[2]变为3,大于m=2,geM不变仍为1

  • 2. len(cnt)=2≥2,geM=1<2,不收缩,left保持1

  • 3. ans += left=1 → ans=2
    calc(2)最终返回 ans=2

    2. 再计算 calc(3):统计不同数≤3、geM≥2 的子数组总数

    数组只有1、2两种数字,len(cnt)永远≤2 <3,全程不会触发窗口收缩逻辑,left始终固定为0
    每一轮 ans += 0,遍历完成后 calc(3)=0

    3. 差分计算最终结果

    calc(2) − calc(3) = 2 − 0 = 2,与题目输出一致。

    四、算法时间复杂度分析

    1. calc函数执行两次:calc(k)、calc(k+1),两次逻辑完全相同;

  • 2. 单次calc内滑动窗口特性:right指针完整遍历数组一次(O(n)),left指针只会单向右移,最多移动n次,无回退;哈希表增删改查单次操作均为均摊O(1);

  • 3. 单次calc时间复杂度 O(n),两次调用总时间复杂度O(n),n为nums数组长度;
    可满足题目 n ≤ 1e5 的数据规模。

    五、算法额外空间复杂度分析

    仅哈希表cnt占用额外空间,哈希表存储窗口内不重复数字;
    最坏情况数组所有数字互不相同,哈希表存储全部n个数字;
    额外空间复杂度O(n)

    Go完整代码如下:

    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)
    }

    2026-06-29:统计包含 K 个不同整数的子数组。用go语言,给定一个整数数组 nums,以及两个整数 k 和 m。你需要统计数组中连续的非空子数

    Python完整代码如下:

    # -*-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)

    2026-06-29:统计包含 K 个不同整数的子数组。用go语言,给定一个整数数组 nums,以及两个整数 k 和 m。你需要统计数组中连续的非空子数

    C++完整代码如下:

      
    



    using namespace std;

    long long countSubarrays(const vector& nums, int k, int m) {
    // 内部函数:统计满足 不同元素个数 >= distinctLimit 且
    // 出现次数 >= m 的元素个数 >= k 的子数组个数
    auto calc = [&](int distinctLimit) -> long long {
    unordered_map 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 nums = {1, 2, 1, 2, 2};
    int k = 2, m = 2;
    long long result = countSubarrays(nums, k, m);
    cout << result << endl;
    return0;
    }

    2026-06-29:统计包含 K 个不同整数的子数组。用go语言,给定一个整数数组 nums,以及两个整数 k 和 m。你需要统计数组中连续的非空子数

    我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

    © 版权声明

    相关文章