You are given an integer array nums and an integer k, return the number of good subarrays of nums.
A good array is an array where the number of different integers in that array is exactly k.
[1,2,3,1,2] has 3 different integers: 1, 2, and 3.A subarray is a contiguous part of an array.
Example 1:
Input: nums = [1,2,1,2,3], k = 2
Output: 7Explanation: Subarrays formed with exactly 2 different integers: [1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2]
Example 2:
Input: nums = [1,2,1,3,4], k = 3
Output: 3Explanation: Subarrays formed with exactly 3 different integers: [1,2,1,3], [2,1,3], [1,3,4].
Example 3:
Input: nums = [1,2,2], k = 1
Output: 4Explanation: Subarrays formed with exactly 1 different integers:
Constraints:
1 <= nums.length <= 20,0001 <= nums[i], k <= nums.lengthBefore attempting this problem, you should be comfortable with:
The most straightforward approach is to check every possible subarray and count those with exactly k distinct integers. For each starting index, we expand the subarray one element at a time, tracking distinct values using a set. Once we exceed k distinct values, we stop and move to the next starting position.
res to store the count of valid subarrays.i:j from i to the end.k, break out of the inner loop.k, increment res.res.Counting subarrays with exactly k distinct values is tricky because adding an element can either increase or maintain the count of distinct values. A clever observation is that we can reframe the problem: subarrays with exactly k distinct = subarrays with at most k distinct minus subarrays with at most k-1 distinct. Counting "at most k" is easier using a sliding window that shrinks whenever we exceed k distinct values.
atMostK(k) that counts subarrays with at most k distinct values:l and r to define the window.r and add each element to the map.k, shrink from l until we have at most k distinct.r, add (r - l + 1) to the result, representing all valid subarrays ending at r.atMostK(k) - atMostK(k - 1).class Solution:
def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:
def atMostK(k):
count = defaultdict(int)
res = l = 0
for r in range(len(nums)):
count[nums[r]] += 1
if count[nums[r]] == 1:
k -= 1
while k < 0:
count[nums[l]] -= 1
if count[nums[l]] == 0:
k += 1
l += 1
res += (r - l + 1)
return res
return atMostK(k) - atMostK(k - 1)Instead of computing "at most k" twice, we can count exactly k in a single pass by tracking a range of valid left boundaries. For any right pointer position with exactly k distinct values, there may be multiple valid starting positions. We maintain two left pointers: l_far marks the leftmost valid start, and l_near marks the rightmost valid start (where the leftmost element appears exactly once). The count of valid subarrays ending at r is l_near - l_far + 1.
l_far and l_near to 0, and a frequency map count.r:nums[r] to the frequency map.k, shrink by moving l_near right and removing elements until we have k distinct. Set l_far = l_near.1, decrement its count and move l_near right.k distinct values, add l_near - l_far + 1 to the result.class Solution:
def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:
count = defaultdict(int)
res = 0
l_far = 0
l_near = 0
for r in range(len(nums)):
count[nums[r]] += 1
while len(count) > k:
count[nums[l_near]] -= 1
if count[nums[l_near]] == 0:
count.pop(nums[l_near])
l_near += 1
l_far = l_near
while count[nums[l_near]] > 1:
count[nums[l_near]] -= 1
l_near += 1
if len(count) == k:
res += l_near - l_far + 1
return resThis approach simplifies the previous one by using a single left pointer and a counter cnt to track how many positions we can extend the left boundary. When we have exactly k distinct values, we shrink the window from the left as long as the leftmost element has duplicates, incrementing cnt each time. The number of valid subarrays ending at the current position is cnt + 1.
count of size n + 1 for frequency tracking (since values are in range [1, n]).l, cnt, and res to 0.r:count[nums[r]]. If this is a new distinct value, decrement k.k < 0 (more than k distinct), decrement count[nums[l]], increment l, increment k, and reset cnt = 0.k == 0 (exactly k distinct), shrink from the left while count[nums[l]] > 1, incrementing cnt each time.cnt + 1 to res.res.class Solution:
def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:
n = len(nums)
count = [0] * (n + 1)
res = l = cnt = 0
for r in range(n):
count[nums[r]] += 1
if count[nums[r]] == 1:
k -= 1
if k < 0:
count[nums[l]] -= 1
l += 1
k += 1
cnt = 0
if k == 0:
while count[nums[l]] > 1:
count[nums[l]] -= 1
l += 1
cnt += 1
res += (cnt + 1)
return resA single sliding window can only count subarrays with "at most k" distinct values, not "exactly k." To count exactly k, you must use the formula atMostK(k) - atMostK(k-1) or maintain two pointers to track a range of valid left boundaries.
When counting subarrays ending at position r, the count is r - l + 1, not r - l. This represents all subarrays starting at any position from l to r and ending at r. Forgetting the +1 leads to undercounting.
When a new element is added, increment the distinct count only if its frequency becomes 1 (first occurrence). When removing, decrement the distinct count only when its frequency drops to 0. Using set size directly on every operation is less efficient and error-prone.
In the one-pass sliding window variant, when the distinct count exceeds k, you must reset the accumulated count cnt to 0. Forgetting this reset causes the algorithm to incorrectly carry over counts from invalid window states.
The one-pass approach uses two left pointers: l_far (leftmost valid start) and l_near (rightmost valid start where the leftmost element appears exactly once). Mixing up their roles or forgetting to update l_far = l_near when shrinking past k distinct values produces incorrect results.