📌  相关文章
📜  具有恰好K个不同元素的子数组的数量(1)

📅  最后修改于: 2023-12-03 15:22:35.105000             🧑  作者: Mango

具有恰好K个不同元素的子数组的数量

介绍

给定一个整数数组和一个整数K,找出恰好包含K个不同元素的子数组的数量。这个问题是一个经典的滑动窗口问题,可以用不同的方法来解决。

解决方法
1. 滑动窗口法

滑动窗口是解决这个问题的一种常见方法。我们可以使用两个指针left和right,它们的位置定义了一个子数组。具体来说,我们通过增加right指针的值来扩展子数组,直到找到一个包含恰好K个不同元素的子数组。然后让left指针增加,直到子数组不再包含恰好K个不同元素。我们重复这个过程,直到right指针到达数组的末尾为止。在这个过程中,我们可以统计每一个包含恰好K个不同元素的子数组的数量。

def subarraysWithKDistinct(nums: List[int], k: int) -> int:
    n = len(nums)
    # 维护一个字典,记录子数组中每个元素的出现次数
    freq = {}
    left1, left2, right = 0, 0, 0
    ans = 0
    while right < n:
        # 更新出现次数
        freq[nums[right]] = freq.get(nums[right], 0) + 1
        # 如果freq的大小超过了k,那么left1指针往右移动,直到freq的大小为k-1
        while len(freq) > k:
            freq[nums[left1]] -= 1
            if freq[nums[left1]] == 0:
                del freq[nums[left1]]
            left1 += 1
        # 如果freq的大小等于k,那么left2指针往右移动,直到freq的大小小于k
        while len(freq) == k:
            freq[nums[left2]] -= 1
            if freq[nums[left2]] == 0:
                del freq[nums[left2]]
            left2 += 1
        # 统计子数组的数量
        ans += left2 - left1
        right += 1
    return ans
2. 前缀和法

我们可以使用前缀和来计算包含K个不同元素的子数组的数量,假设我们有一个数组arr和一个整数K,其前缀和数组为preSum。我们可以通过枚举左右边界l和r,并计算preSum[r] - preSum[l - 1]的值,然后统计其中包含恰好K个不同元素的子数组的数量。为了计算preSum,我们需要先将数组arr按元素从小到大排序。然后我们可以使用二分查找来计算preSum数组。

from bisect import bisect_left

def subarraysWithKDistinct(nums: List[int], k: int) -> int:
    n = len(nums)
    # 排序,为了可以使用二分查找
    sortedNums = sorted(set(nums))
    s = [bisect_left(sortedNums, num) for num in nums]
    # 统计前缀和
    preSum = [0] * (n + 1)
    for i in range(1, n + 1):
        preSum[i] = preSum[i - 1] + s[i - 1]
    # 统计子数组的数量
    freq = [0] * (n + 1)
    ans, left = 0, 1
    for right in range(1, n + 1):
        # 更新freq
        freq[s[right - 1]] += 1
        # 如果freq的大小超过了k,那么left指针往右移动,直到freq的大小为k-1
        while len(set(nums[left - 1:right])) > k:
            freq[s[left - 1]] -= 1
            left += 1
        # 如果freq的大小等于k,那么统计子数组的数量
        if len(set(nums[left - 1:right])) == k:
            ans += preSum[right] - preSum[left - 1]
    return ans
总结

这个问题可以用不同的方法来解决,比如滑动窗口和前缀和。其中,滑动窗口方法是更通用的方法,可以解决很多类似的问题。而前缀和方法则更适合处理区间和相关的问题。