AlgoMaster Logo

Subarrays with K Different Integers

hardFrequencyUpdated September 21, 2026

Understanding the Problem

We need to count all contiguous subarrays that contain exactly k distinct integers. Not "at most k," not "at least k," but precisely k.

The "exactly k" constraint is the source of the difficulty. A sliding window handles "at most" thresholds well: expand the right side, and when the constraint breaks, shrink from the left. "Exactly k" has no such clean shrink rule. A window below k distinct values needs to expand, a window above k needs to shrink, but when it sits at exactly k, there can be several valid subarrays ending at the same right pointer with different left boundaries, so counting one and moving on is wrong.

The standard transformation is exactly(k) = atMost(k) - atMost(k-1). Count subarrays with at most k distinct integers, subtract the count with at most k-1 distinct integers, and the difference is the count with exactly k distinct integers. "At most k" is a problem a single sliding window solves cleanly.

Key Constraints:

  • 1 <= nums.length <= 2 * 10^4 → With n up to 20,000, an O(n^2) brute force is around 400 million operations, which risks a time limit. An O(n) sliding window is the target.
  • 1 <= nums[i], k <= nums.length → Values are positive integers bounded by n, so a plain array of size n+1 works as a frequency map instead of a hash map, with O(1) lookups and a lower constant factor.

Approach 1: Brute Force

Intuition

Check every possible subarray. For each pair of indices (i, j) where i <= j, count the distinct integers in nums[i..j] and increment the answer if that count equals k.

Distinct integers can be tracked with a frequency map whose size gives the distinct count. Rather than rebuild the map for each (i, j) pair, fix the start index i and extend the end index j one element at a time, updating the same map. That keeps the inner work O(1) per step and brings the total from O(n^3) down to O(n^2).

Algorithm

  1. Initialize a counter result = 0.
  2. For each starting index i from 0 to n-1:
    • Create an empty frequency map for the window starting at i.
    • For each ending index j from i to n-1:
      • Increment the frequency of nums[j] in the map.
      • If the map now holds exactly k distinct keys, increment result.
  3. Return result.

Visualization and Code

Loading animation...

For each starting index, the inner loop scans every ending index, up to 400 million iterations at the maximum n. The next approach reuses window state across positions instead of restarting from each index.

Approach 2: Sliding Window (Exactly K = At Most K - At Most K-1)

Intuition

Counting subarrays with at most k distinct integers fits a single sliding window. Maintain a window [left, right]. When the distinct count exceeds k, shrink from the left until it drops back to k. At each position of right, the number of valid subarrays ending at right is (right - left + 1), since every start index from left to right produces a window with at most k distinct values.

The decomposition exactly(k) = atMost(k) - atMost(k-1) then gives the answer directly. Every subarray counted in atMost(k) has either exactly k distinct values or fewer. Subtracting atMost(k-1) removes those with fewer than k, leaving only the ones with exactly k. One helper function, atMost(k), called twice.

Algorithm

  1. Define a helper function atMost(nums, k) that counts subarrays with at most k distinct integers:
    • Initialize left = 0, result = 0, and an empty frequency map.
    • For each right from 0 to n-1:
      • Add nums[right] to the frequency map.
      • While the number of distinct values exceeds k, decrement the frequency of nums[left], remove it if zero, and move left forward.
      • Add (right - left + 1) to result.
    • Return result.
  2. Return atMost(nums, k) - atMost(nums, k - 1).

Visualization and Code

Loading animation...

This approach makes two full passes over the array. The next approach collapses them into one pass by tracking two left pointers at the same time.

Approach 3: Single-Pass Sliding Window (Two Left Pointers)

Intuition

The subtraction approach computes atMost(k) and atMost(k-1) in separate passes. Both quantities can be tracked together, in a single pass, using two left pointers sharing one right pointer.

For a fixed right endpoint, let left1 be the smallest index where [left1, right] has at most k distinct values, and left2 the smallest index where [left2, right] has at most k-1 distinct values. The number of subarrays ending at right with exactly k distinct values is then left2 - left1: every start index from left1 to left2 - 1 produces a window with at most k distinct values but more than k-1, which is exactly k.

Both windows advance as right moves. The invariant left2 >= left1 always holds, since the stricter "at most k-1" constraint forces window 2's left edge to be at or ahead of window 1's.

Algorithm

  1. Initialize two left pointers left1 = 0 and left2 = 0, two frequency maps, two distinct counters, and result = 0.
  2. For each right from 0 to n-1:
    • Add nums[right] to both windows.
    • Shrink window 1: while distinct1 > k, remove nums[left1] and advance left1.
    • Shrink window 2: while distinct2 > k - 1, remove nums[left2] and advance left2.
    • Add left2 - left1 to result.
  3. Return result.

Visualization and Code

Loading animation...