【方向1】知识总结——好子数组的计数

127 阅读3分钟

1、题目

小M有一个由正整数组成的数组 nums,她想知道其中有多少子数组可以被称为「好子数组」。一个子数组被称为「好子数组」是指它包含的不同整数个数恰好为 k

例如,对于数组 [1, 2, 3, 1, 2],其中包含 3 个不同的整数:12 和 3

子数组指的是数组的连续部分。小M需要找出所有满足条件的子数组,并计算它们的数量。

2、题目分析

小M想找出数组 nums 中包含 恰好 k 个不同整数 的子数组数量,这是一个典型的滑动窗口问题。子数组必须是连续的,因此我们可以通过控制窗口的左右边界来高效解决问题。

3、解法:滑动窗口+哈希表

核心思路是通过维护两个滑动窗口计算子数组中「最多包含 k 个不同整数」和「最多包含 k-1 个不同整数」的数量之差,从而得到 恰好包含 k 个不同整数 的子数组数量。

4、实现步骤

  1. 辅助函数 at_most_k(nums, k)

    • 利用滑动窗口和哈希表(defaultdict)统计 最多包含 k 个不同整数 的子数组数量。
    • 右边界扩展窗口时,加入当前整数;若窗口内的不同整数数量超出 k,则收缩左边界。
    • 统计以右边界为终点的合法子数组数量。
  2. 主函数 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,则移动左边界(收缩窗口)。
    • 每次更新窗口后,计算从 leftright 的所有子数组数量。
  • 时间复杂度:O(n)O(n)O(n),因为左右指针最多各遍历整个数组一次。

2. 差分计算恰好包含 k 个不同整数

  • 子数组包含的不同整数小于等于 k 时,包含的不同整数必然同时小于等于 k-1。
  • 差值操作剔除了多余的情况,得到了恰好包含 k 个不同整数的子数组数量。

结论

这道题通过滑动窗口技巧高效解决了计算「好子数组」数量的问题,具备很强的实战价值。理解滑动窗口的思想、哈希表的动态更新是解题的关键。