1、题目
小M有一个由正整数组成的数组 nums,她想知道其中有多少子数组可以被称为「好子数组」。一个子数组被称为「好子数组」是指它包含的不同整数个数恰好为 k。
例如,对于数组 [1, 2, 3, 1, 2],其中包含 3 个不同的整数:1、2 和 3。
子数组指的是数组的连续部分。小M需要找出所有满足条件的子数组,并计算它们的数量。
2、题目分析
小M想找出数组 nums 中包含 恰好 k 个不同整数 的子数组数量,这是一个典型的滑动窗口问题。子数组必须是连续的,因此我们可以通过控制窗口的左右边界来高效解决问题。
3、解法:滑动窗口+哈希表
核心思路是通过维护两个滑动窗口计算子数组中「最多包含 k 个不同整数」和「最多包含 k-1 个不同整数」的数量之差,从而得到 恰好包含 k 个不同整数 的子数组数量。
4、实现步骤
-
辅助函数
at_most_k(nums, k):- 利用滑动窗口和哈希表(
defaultdict)统计 最多包含 k 个不同整数 的子数组数量。 - 右边界扩展窗口时,加入当前整数;若窗口内的不同整数数量超出 k,则收缩左边界。
- 统计以右边界为终点的合法子数组数量。
- 利用滑动窗口和哈希表(
-
主函数
solution(nums, k):- 调用两次
at_most_k(nums, k),分别计算 最多包含 k 个不同整数 和 最多包含 k-1 个不同整数 的子数组数量。 - 两者相减得到结果。
- 调用两次
5、代码
from collections import defaultdict
def at_most_k(nums, k):
"""计算子数组中最多包含 k 个不同整数的数量"""
left = 0
count = defaultdict(int)
total = 0
for right in range(len(nums)):
count[nums[right]] += 1
# 如果新加入的数字导致不同整数的数量超过 k,收缩窗口
while len(count) > k:
count[nums[left]] -= 1
if count[nums[left]] == 0:
del count[nums[left]]
left += 1
# 计算以 right 为右边界的所有合法子数组数量
total += right - left + 1
return total
def solution(nums, k):
"""计算恰好包含 k 个不同整数的子数组数量"""
return at_most_k(nums, k) - at_most_k(nums, k - 1)
# 测试样例
if __name__ == '__main__':
print(solution(nums=[1, 2, 1, 2, 3], k=2)) # 输出: 7
print(solution(nums=[1, 2, 1, 3, 2], k=3)) # 输出: 5
print(solution(nums=[1, 1, 1, 1], k=1)) # 输出: 10
5、代码解析
1. 函数 at_most_k(nums, k) 的作用
-
维护窗口
[left, right],动态调整以确保最多包含 k 个不同整数。 -
len(count)用于判断窗口内的不同整数数量:- 如果超出 k,则移动左边界(收缩窗口)。
- 每次更新窗口后,计算从
left到right的所有子数组数量。
-
时间复杂度:O(n)O(n)O(n),因为左右指针最多各遍历整个数组一次。
2. 差分计算恰好包含 k 个不同整数
- 子数组包含的不同整数小于等于 k 时,包含的不同整数必然同时小于等于 k-1。
- 差值操作剔除了多余的情况,得到了恰好包含 k 个不同整数的子数组数量。
结论
这道题通过滑动窗口技巧高效解决了计算「好子数组」数量的问题,具备很强的实战价值。理解滑动窗口的思想、哈希表的动态更新是解题的关键。