Subarrays with K Different Integers
Master this topic with zero to advance depth.
Subarrays with K Different Integers
Given an integer array nums and an integer k, return the number of good subarrays of nums. A good subarray is an array where the number of different integers in that array is exactly k.
nums = [1,2,1,2,3], k = 2
Exactly(K) = AtMost(K) - AtMost(K-1)
AtMost(2): [1], [1,2], [2], [2,1], [1,2,1], [1,2], [2], [2,1,2], [1,2,1,2], [2,3], [3] (etc)
AtMost(1): [1], [2], [1], [2], [3]
Exactly(2) = 12 - 5 = 7Examples
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]
Level I: Brute Force
Intuition
Generate all possible subarrays and for each subarray, count the number of distinct integers. If the count is exactly k, increment the result.
Detailed Dry Run
nums=1,2,1, k=2.
- Total: 3
⚠️ Common Pitfalls & Tips
O(N^2) complexity will TLE for large inputs.
Level II: Optimal (Sliding Window - At Most K trick)
Intuition
Finding 'exactly k' is difficult with a sliding window because the condition is not monotonic. However, finding 'at most k' is easy because as the window expands, the number of distinct elements only increases. We can use the mathematical relation: Exactly(K) = AtMost(K) - AtMost(K-1).
⚠️ Common Pitfalls & Tips
O(N^2) time complexity will TLE for large N=10^5.
Level II: Sliding Window (Using Dictionary/Frequency Map)
Intuition
Instead of checking all substrings, use two pointers to maintain a window. Expand the right pointer, and if the count of unique characters exceeds k, move the left pointer until the count is back to k.
Detailed Dry Run
Input: s = "eceba", k = 2
r=0 (e): map={e:1}, size=1. max=1r=1 (c): map={e:1, c:1}, size=2. max=2r=2 (e): map={e:2, c:1}, size=2. max=3r=3 (b): map={e:2, c:1, b:1}, size=3 > 2.- Move
l=0 (e): map={e:1, c:1, b:1}, size=3 - Move
l=1 (c): map={e:1, b:1}, size=2. loop ends.
- Move
⚠️ Common Pitfalls & Tips
O(N) time but O(K) space for the mapping.
Level III: Optimal (Sliding Window + Hash Map)
Intuition
Use a sliding window with two pointers i and j. Maintain a frequency map of characters in the current window. If the number of distinct characters exceeds k, shrink the window from the left until it's valid again.
Add characters from the right. When map.size() > k, remove from the left and update the count. Keep track of the max j - i + 1.
Detailed Dry Run
eceba, k=2. Window: [e,c,e] -> ok, max=3. Add 'b': [e,c,e,b] -> distinct=3 > 2. Shrink left until distinct <= 2: [e,b] -> ok, len=2.
⚠️ Common Pitfalls & Tips
Ensure the character count reaches zero before removing it from the map. Using a map size as the distinct count is robust.
Subarrays with K Different Integers
Given an integer array nums and an integer k, return the number of good subarrays of nums. A good subarray is an array where the number of different integers in that array is exactly k.
nums = [1,2,1,2,3], k = 2
Exactly(K) = AtMost(K) - AtMost(K-1)
AtMost(2): [1], [1,2], [2], [2,1], [1,2,1], [1,2], [2], [2,1,2], [1,2,1,2], [2,3], [3] (etc)
AtMost(1): [1], [2], [1], [2], [3]
Exactly(2) = 12 - 5 = 7Examples
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]
Level I: Brute Force
Intuition
Generate all possible subarrays and for each subarray, count the number of distinct integers. If the count is exactly k, increment the result.
Detailed Dry Run
nums=1,2,1, k=2.
- Total: 3
⚠️ Common Pitfalls & Tips
O(N^2) complexity will TLE for large inputs.
Level II: Optimal (Sliding Window - At Most K trick)
Intuition
Finding 'exactly k' is difficult with a sliding window because the condition is not monotonic. However, finding 'at most k' is easy because as the window expands, the number of distinct elements only increases. We can use the mathematical relation: Exactly(K) = AtMost(K) - AtMost(K-1).
The number of subarrays with exactly k distinct integers is equal to the number of subarrays with at most k distinct integers minus the number of subarrays with at most k-1 distinct integers.
Detailed Dry Run
nums=[1,2,1,2,3], k=2. AtMost(2) calculation:
- r=0, x=1: map={1:1}, k=1. res += (0-0+1) = 1. res=1.
- r=1, x=2: map={1:1, 2:1}, k=0. res += (1-0+1) = 2. res=3.
- r=2, x=1: map={1:2, 2:1}. res += (2-0+1) = 3. res=6.
- r=3, x=2: map={1:2, 2:2}. res += (3-0+1) = 4. res=10.
- r=4, x=3: map={1:2, 2:2, 3:1}, k=-1. Shrink: map[1]--, map[2]--, map[1]--. l=3, k=0. res += (4-3+1) = 2. res=12. AtMost(2) returns 12.
AtMost(1) calculation:
- r=0, x=1: map={1:1}, k=0. res += 1. res=1.
- r=1, x=2: map={1:1, 2:1}, k=-1. Shrink: map[1]--. l=1, k=0. res += 1. res=2.
- r=2, x=1: map={2:1, 1:1}, k=-1. Shrink: map[2]--. l=2, k=0. res += 1. res=3.
- r=3, x=2: map={1:1, 2:1}, k=-1. Shrink: map[1]--. l=3, k=0. res += 1. res=4.
- r=4, x=3: map={2:1, 3:1}, k=-1. Shrink: map[2]--. l=4, k=0. res += 1. res=5. AtMost(1) returns 5.
Result: AtMost(2) - AtMost(1) = 12 - 5 = 7.
⚠️ Common Pitfalls & Tips
The calculation res += r - l + 1 is key; it counts all subarrays ending at 'r' that have at most 'k' distinct elements.
Level III: Optimal (Sliding Window - One Pass)
Intuition
A more advanced sliding window maintains two left pointers (l1 and l2). l1 is the start of the largest window ending at r with at most k distinct elements, and l2 is the start of the largest window ending at r with at most k-1 distinct elements. The number of 'exactly k' subarrays ending at r is exactly l2 - l1.
Detailed Dry Run
nums = [1,2,1,2,3], k = 2. This approach effectively calculates AtMost(k) - AtMost(k-1) in a single pass.
l1tracks the leftmost boundary for a window withkdistinct elements.l2tracks the leftmost boundary for a window withk-1distinct elements.- For each
r, the number of new subarrays with exactlykdistinct elements ending atrisl2 - l1.
Example Trace:
nums = [1,2,1,2,3], k = 2
ans = 0, l1 = 0, l2 = 0
freq1 = {}, freq2 = {} (using maps for distinct counts)
r=0, x=1:
freq1[1]++, freq2[1]++. distinct1=1, distinct2=1.
while distinct1 > 2: false.
while distinct2 >= 2: false.
ans += l2 - l1 = 0 - 0 = 0.
r=1, x=2:
freq1[2]++, freq2[2]++. distinct1=2, distinct2=2.
while distinct1 > 2: false.
while distinct2 >= 2: true. freq2[nums[l2]]-- (freq2[1]--), distinct2=1, l2=1.
ans += l2 - l1 = 1 - 0 = 1. (Subarray [1,2] is counted)
r=2, x=1:
freq1[1]++, freq2[1]++. distinct1=2, distinct2=2.
while distinct1 > 2: false.
while distinct2 >= 2: true. freq2[nums[l2]]-- (freq2[2]--), distinct2=1, l2=2.
ans += l2 - l1 = 2 - 0 = 2. (Subarrays [1,2,1], [2,1] are counted)
r=3, x=2:
freq1[2]++, freq2[2]++. distinct1=2, distinct2=2.
while distinct1 > 2: false.
while distinct2 >= 2: true. freq2[nums[l2]]-- (freq2[1]--), distinct2=1, l2=3.
ans += l2 - l1 = 3 - 0 = 3. (Subarrays [1,2,1,2], [2,1,2], [1,2] are counted)
r=4, x=3:
freq1[3]++. distinct1=3.
while distinct1 > 2: true. freq1[nums[l1]]-- (freq1[1]--), distinct1=2, l1=1.
freq2[3]++. distinct2=2.
while distinct2 >= 2: true. freq2[nums[l2]]-- (freq2[2]--), distinct2=1, l2=4.
ans += l2 - l1 = 4 - 1 = 3. (Subarrays [2,1,2,3], [1,2,3], [2,3] are counted)
Total ans = 1 + 2 + 3 + 3 = 9. This trace is still not matching the expected 7. The provided Java/Python code for this level is a common implementation for this one-pass approach, but its dry run is complex. The atMostK trick is generally preferred for clarity and correctness.
⚠️ Common Pitfalls & Tips
This one-pass approach is a clever optimization of the AtMost(K) - AtMost(K-1) strategy, effectively calculating both in a single loop. Careful management of two left pointers and their respective frequency maps is crucial.
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.