Subarrays with K Different Integers
Subarrays with K Different Integers: return the number of subarrays containing exactly k distinct integers.
- 1 <= nums.length <= 2 * 10⁴
- 1 <= nums[i], k <= nums.length
Intuition
Subarrays with k different integers counts subarrays containing exactly k distinct values. A sliding window tracks distinct counts easily, but counting windows with exactly k distinct values directly does not work.
The reason is worth understanding. For a given right endpoint, the valid left endpoints form a contiguous range, and finding both of its boundaries requires two separate window positions — the window cannot report both at once.
The standard resolution converts the exact count into a difference:
- exactly(k) = atMost(k) − atMost(k − 1), because subarrays with at most k distinct values that are not counted by at most k − 1 are precisely those with exactly k.
Counting at most k distinct values slides cleanly. Extend the right edge, and while the distinct count exceeds k, shrink from the left. At each position, the number of valid subarrays ending there is right − left + 1 — every left endpoint in the current window produces a valid subarray.
That counting step is the part most often written wrong. Adding 1 per position instead of the window width undercounts massively.
Within atMost, a value is removed from the frequency map only when its count reaches zero, not on the first decrement. Deleting early miscounts the distinct total.
The same identity solves Binary Subarrays with Sum and Count Number of Nice Subarrays — recognising it turns three problems into one technique.
Two passes of a linear window give O(n) time and O(k) space.
The same at-most subtraction, now with distinct-count as the window condition. atMost(k) - atMost(k-1) isolates subarrays with exactly k distinct values, and each at-most call is the standard k-distinct window that deletes zero-count keys.
Approach
Before reading on: price up what the direct approach costs here, then ask what makes a window invalid, and which pointer should move when it is. Aim for O(n) time and O(k) space.
See why exact counting fails
For each right endpoint the valid left endpoints form a range, and a single window position cannot report both of its boundaries at once.
Convert to a difference
exactly(k) = atMost(k) − atMost(k − 1). Subarrays with at most k distinct values but not at most k − 1 are precisely those with exactly k.
Slide for at most k
Extend the right edge, and while the distinct count exceeds k, shrink from the left. This window slides cleanly where an exact one cannot.
Count the window width
Add right − left + 1 at each position, not 1. Every left endpoint in the current window forms a valid subarray — adding 1 undercounts massively.
Remove keys only at zero
Delete a value from the map only when its count reaches 0. Removing on the first decrement miscounts the distinct total.
Recognise the wider pattern
The same identity solves Binary Subarrays with Sum and Count Number of Nice Subarrays — one technique across three problems.
Cost of the approach
Two linear window passes give O(n) time and O(k) space for the frequency map.
Solution & live demo
Common pitfalls
Keeping zero-count keys in the frequency map
freq[out] -= 1 left += 1
if freq[out] == 0:
del freq[out]len(freq) is the validity test, so a key at zero still counts toward distinctness. The window then over-shrinks and both at-most calls return values that are too small.
Trying to count exactly-k with one window
while len(freq) > k: shrink if len(freq) == k: total += 1
return atMost(k) - atMost(k - 1)
For a given right endpoint the valid left endpoints form a contiguous range, not a single position, and finding both its ends needs two pointers. The subtraction gets the same count with one simple window run twice.
Counting one subarray per window position
total += 1
total += right - left + 1
Every subarray ending at right and starting anywhere in [left, right] satisfies at-most-m. That's right - left + 1 subarrays, and the identity depends on counting them all.
Edge cases
atMost(0) is 0, so the answer is simply the count of constant runs.
Both terms coincide and the difference is 0.
len(freq) overstates the distinct count and the window over-shrinks — the same trap as the k-distinct substring problem.
Only k = 1 gives a non-zero answer, namely n(n+1)/2.