992. Subarrays with K Different Integers - Explanation
Description
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.
- For example,
[1,2,3,1,2]has3different integers:1,2, and3.
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.length
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Sliding Window - The optimal solutions use sliding window technique with variable window sizes to track subarrays
- Hash Maps - Used to track frequency counts of elements within the current window
- Two Pointers - Advanced solutions use multiple left pointers to track ranges of valid starting positions
1. Brute Force
Intuition
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.
Algorithm
- Initialize a result counter
resto store the count of valid subarrays. - For each starting index
i:- Create an empty set to track distinct values.
- Expand the subarray by moving index
jfromito the end. - Add each new element to the set.
- If the set size exceeds
k, break out of the inner loop. - If the set size equals
k, incrementres.
- Return
res.
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Sliding Window
Intuition
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.
Algorithm
- Define a helper function
atMostK(k)that counts subarrays with at mostkdistinct values:- Use two pointers
landrto define the window. - Maintain a frequency map to track counts of elements in the window.
- Expand
rand add each element to the map. - When distinct count exceeds
k, shrink fromluntil we have at mostkdistinct. - For each position of
r, add(r - l + 1)to the result, representing all valid subarrays ending atr.
- Use two pointers
- Return
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)Time & Space Complexity
- Time complexity:
- Space complexity:
3. Sliding Window (One Pass) - I
Intuition
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.
Algorithm
- Initialize
l_farandl_nearto0, and a frequency mapcount. - For each right pointer
r:- Add
nums[r]to the frequency map. - If distinct count exceeds
k, shrink by movingl_nearright and removing elements until we havekdistinct. Setl_far = l_near. - While the leftmost element has count greater than
1, decrement its count and movel_nearright. - If we have exactly
kdistinct values, addl_near - l_far + 1to the result.
- Add
- Return 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 resTime & Space Complexity
- Time complexity:
- Space complexity:
4. Sliding Window (One Pass) - II
Intuition
This 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.
Algorithm
- Create an array
countof sizen + 1for frequency tracking (since values are in range[1, n]). - Initialize
l,cnt, andresto0. - For each right pointer
r:- Increment
count[nums[r]]. If this is a new distinct value, decrementk. - If
k < 0(more than k distinct), decrementcount[nums[l]], incrementl, incrementk, and resetcnt = 0. - If
k == 0(exactly k distinct), shrink from the left whilecount[nums[l]] > 1, incrementingcnteach time. - Add
cnt + 1tores.
- Increment
- Return
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 resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Trying to Count Exactly K Directly with a Single Sliding Window
A 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.
Off-by-One Errors in Subarray Counting
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.
Not Properly Tracking When Distinct Count Changes
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.
Resetting State Incorrectly in the One-Pass Approach
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.
Confusing the Two Left Pointers in One-Pass Solutions
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.
Sign in to join the discussion