疯狂子数组的统计问题刷题笔记 | 豆包MarsCode AI 刷题

44 阅读4分钟

问题描述

小M正在研究数组中的子数组问题。给定一个长度为N的数组 A,编号从8到 N-1。如果一个从索引i到j的子数组包含一个出现至少《次的元素义,我们就称这个 子数组为”"疯狂子数组”。 现在,你需要计算数组 A 中所有的疯狂子数组的数量。

题目分析

我们需要计算一个数组中的“疯狂子数组”的数量。根据题目定义:

  • 子数组是数组的一部分,连续的元素构成。
  • 如果子数组中至少有一个元素的出现次数不小于 K,则这个子数组称为“疯狂子数组”。

输入描述:

  • N: 数组长度(正整数)。
  • K: 判定标准,即子数组中某个元素至少出现的次数。
  • A: 一个长度为 N 的整数数组。

输出描述:

返回所有“疯狂子数组”的数量。

解题思路

暴力方法(不可取):

遍历所有可能的子数组(起点和终点的所有组合)。 对于每个子数组统计每个元素的频率,看是否有元素出现次数 ≥ K。 时间复杂度为 𝑂(𝑁3),不可行。

优化方法(滑动窗口):

  • 滑动窗口可以用来高效处理子数组问题。通过动态维护子数组的边界和条件,避免不必要的重复计算。
  • 在滑动窗口中用一个字典 freq 存储当前窗口内每个元素的频率。
  • 窗口右边界不断右移,直到满足条件(某个元素的频率 ≥ K)。此时,窗口内所有子数组的右端点都是“疯狂子数组”,我们可以一次性统计这些子数组的数量。

代码详解

代码逻辑:

def solution(N: int, K: int, A: list) -> int:
    count = 0  # 用于计数“疯狂子数组”
    freq = {}  # 记录滑动窗口内的频率
    left = 0   # 滑动窗口的左边界
    
    for right in range(N):  # 遍历数组,右边界逐渐扩大
        # 更新右边界加入的元素的频率
        if A[right] in freq:
            freq[A[right]] += 1
        else:
            freq[A[right]] = 1
        
        # 检查当前窗口是否满足条件(某个元素频率 >= K)
        while any(value >= K for value in freq.values()):  
            # 符合条件时,计算疯狂子数组数量
            count += N - right
            # 缩小窗口:移除左边界的元素,并更新频率
            freq[A[left]] -= 1
            if freq[A[left]] == 0:  # 如果元素频率为 0,删除该元素
                del freq[A[left]]
            left += 1  # 左边界右移
    
    return count  # 返回最终计数

代码详解

变量初始化:

  • count: 记录所有“疯狂子数组”的数量。
  • freq: 字典,用于动态维护当前窗口中每个元素的频率。
  • left: 窗口的左边界。

窗口右边界扩展:

  • 遍历数组,通过 right 控制窗口的右边界。
  • 每次新增一个元素时,将其频率加到 freq 中。

- 窗口条件维护:

  • 判断窗口是否满足条件(至少一个元素的频率不小于 K)。
  • 如果满足条件,说明从当前 left 到 right 的所有子数组都是“疯狂子数组”。数量为 N - right。

窗口收缩:

  • 为了找到新的“疯狂子数组”,移动左边界(left),同时更新频率表 freq。
  • 当窗口不再满足条件时停止。

返回结果:

  • count 累积了所有符合条件的子数组数量。

时间复杂度分析

右边界循环:

  • 外层 for 循环遍历数组,执行 𝑂 ( 𝑁 )。

左边界移动:

  • 内层 while 循环,在整个过程中每个元素最多被加入和移出一次,累计执行 𝑂 ( 𝑁 )。

频率判断:

  • 每次判断 any(value >= K for value in freq.values()) 的复杂度为 𝑂 ( 1 ) O(1)(假设字典的键数是有限的)。

综合时间复杂度为 𝑂 ( 𝑁 )。

总结

问题的本质:

  • 本题是典型的滑动窗口问题,目的是动态维护窗口条件,并高效计算满足条件的子数组数量。

关键点:

  • 滑动窗口的动态收缩和扩展。
  • 使用字典动态维护窗口内的频率,保证时间复杂度。

优点:

  • 避免了暴力枚举的重复计算,时间复杂度从 𝑂 ( 𝑁 3 )降到了 𝑂 ( 𝑁 )。
  • 代码清晰、逻辑简洁,非常适合处理这类“连续区间 + 条件”的问题。

扩展思考

其他类似问题:

  • 找到满足条件的最长子数组。
  • 统计满足条件的子数组的起始位置。

优化空间:

  • 判断 freq 是否满足条件的操作在特殊情况下可能更高效。
  • 如果子数组元素范围较小,可以用数组替代字典。 通过滑动窗口和频率统计,这种问题都可以在高效的时间复杂度内解决。