第472题 疯狂子数组的统计
1.题目分析
给定一个数组 A 和一个整数 K,我们需要找到数组中所有的“疯狂子数组”的数量。子数组的定义是:数组中一段连续的子序列,称为“疯狂子数组”的条件是它包含一个出现次数至少为 K 的元素。
关键点:
- 子数组的左右边界是动态变化的,我们需要有效地统计满足条件的子数组。
- 滑动窗口可以高效管理子数组的范围,并动态维护子数组中元素的出现次数。
目标:
计算所有满足条件的子数组数量。要做到这一点,需避免重复遍历,提升效率。
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,表示窗口逐渐扩展:
- 更新当前元素的出现次数。
- 如果该元素的出现次数恰好达到 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. 可扩展性:
此方法可以扩展到其他类似的子数组计数问题,只需更改窗口条件的判断逻辑即可。