青训营X豆包MarsCode 技术训练营第十四课 | 豆包MarsCode AI 刷题

45 阅读3分钟

第472题 疯狂子数组的统计

1.题目分析

给定一个数组 A 和一个整数 K,我们需要找到数组中所有的“疯狂子数组”的数量。子数组的定义是:数组中一段连续的子序列,称为“疯狂子数组”的条件是它包含一个出现次数至少为 K 的元素。

关键点

  1. 子数组的左右边界是动态变化的,我们需要有效地统计满足条件的子数组。
  2. 滑动窗口可以高效管理子数组的范围,并动态维护子数组中元素的出现次数。

目标

计算所有满足条件的子数组数量。要做到这一点,需避免重复遍历,提升效率。

2.解题思路

1. 滑动窗口

利用两个指针 lll 和 rrr 表示子数组的左右边界,通过扩展 rrr 来探索子数组,并通过移动 lll 缩减窗口范围。

2. 动态维护窗口内的状态

通过一个哈希表记录窗口内每个元素的出现次数,方便判断是否有元素满足出现次数 ≥K\geq K≥K 的条件。

3. 子数组数量的计算

当窗口内满足“疯狂子数组”条件时,从当前左边界 lll 到右边界 rrr 的所有子数组都符合条件。这些子数组的数量为 r−l+1r - l + 1r−l+1。

4. 优化移动逻辑

窗口的左边界 lll 只在必要时移动,确保滑动窗口的效率为 O(N)O(N)O(N)。

3.解题代码

def solution(N: int, K: int, A: list) -> int:
# 滑动窗口及哈希表
    count = {}
    l = 0
    result = 0
    crazy_count = 0

    for r in range(N):
        # 将当前元素加入窗口
        count[A[r]] = count.get(A[r], 0) + 1

        # 检查当前窗口是否包含至少一个出现 K 次的元素
        if count[A[r]] == K:
            crazy_count += 1
        
        # 左边界移动时,更新状态
        while crazy_count > 0:
            result += N - r  # 窗口内以 l 开头的子数组数量
            count[A[l]] -= 1
            if count[A[l]] == K - 1:
                crazy_count -= 1
            l += 1
    
    return result

if __name__ == '__main__':
    print(solution(N = 5, K = 2, A = [1, 5, 2, 5, 2]) == 5)
    print(solution(N = 6, K = 3, A = [1, 1, 2, 2, 1, 2]) == 4)
    print(solution(N = 4, K = 2, A = [4, 4, 4, 4]) == 6)

4.模块解释

1. 初始化变量

  • count:哈希表,记录当前窗口内各元素的出现次数。
  • l:窗口左边界,初始为 0。
  • result:记录满足条件的子数组总数量。
  • crazy_count:标记当前窗口内是否有满足出现 ≥K\geq K≥K 次的元素。

2. 遍历右边界 rrr

右边界从 0 遍历到 N−1N-1N−1,表示窗口逐渐扩展:

  1. 更新当前元素的出现次数。
  2. 如果该元素的出现次数恰好达到 KKK,表示窗口内新增了一个满足条件的元素。

3. 判断是否满足条件

当窗口内有至少一个元素满足 ≥K\geq K≥K 次:

  • 从左边界 lll 到右边界 rrr 的所有子数组均为“疯狂子数组”,数量为 r−l+1r - l + 1r−l+1。

4. 左边界移动

移动 lll 时,减小窗口内的状态,若某元素的次数减少到 K−1K-1K−1,取消其作为“疯狂子数组”的条件。

5. 累加结果

在整个过程中,累加每次 rrr 扩展时新增的子数组数量。

总结

1. 算法复杂度

  • 时间复杂度:O(N)O(N)O(N),每个元素最多进出窗口一次。
  • 空间复杂度:O(U)O(U)O(U),其中 UUU 是数组中不同元素的种类数。

2. 关键优化

滑动窗口将子数组的遍历从暴力枚举(O(N2)O(N^2)O(N2))优化为线性复杂度,同时动态维护状态避免冗余计算。

3. 可扩展性

此方法可以扩展到其他类似的子数组计数问题,只需更改窗口条件的判断逻辑即可。