Subarrays with K Different Integers
Count subarrays with exactly K distinct integers — atMost(K) − atMost(K−1).
4 min read
sliding-window hash-map
Problem#
Return the number of contiguous subarrays of nums containing exactly k distinct integers.
Examples#
nums = [1,2,1,2,3], k = 2→7nums = [1,2,1,3,4], k = 3→3
Constraints#
1 <= nums.length, k <= nums.length.
Hints#
Hint 1
exactly(K) = atMost(K) − atMost(K−1). Each atMost is a clean variable window. Approach#
Implement atMost(K) = number of subarrays with at most K distinct integers using the variable-window template. The exact count is the difference.
Solution#
class Solution {public: int subarraysWithKDistinct(vector<int>& nums, int k) { return atMost(nums, k) - atMost(nums, k - 1); }private: int atMost(vector<int>& nums, int k) { if (k < 0) return 0; unordered_map<int,int> freq; int left = 0, count = 0; for (int right = 0; right < (int)nums.size(); ++right) { ++freq[nums[right]]; while ((int)freq.size() > k) { if (--freq[nums[left]] == 0) freq.erase(nums[left]); ++left; } count += right - left + 1; // # of subarrays ending at `right` with ≤ k distinct } return count; }};func subarraysWithKDistinct(nums []int, k int) int { return atMostKDistinct(nums, k) - atMostKDistinct(nums, k-1)}
func atMostKDistinct(nums []int, k int) int { if k < 0 { return 0 } freq := map[int]int{} left, count := 0, 0 for right := 0; right < len(nums); right++ { freq[nums[right]]++ for len(freq) > k { freq[nums[left]]-- if freq[nums[left]] == 0 { delete(freq, nums[left]) } left++ } count += right - left + 1 // # of subarrays ending at right with ≤ k distinct } return count}from collections import defaultdictfrom typing import List
class Solution: def subarraysWithKDistinct(self, nums: List[int], k: int) -> int: return self._at_most(nums, k) - self._at_most(nums, k - 1)
def _at_most(self, nums: List[int], k: int) -> int: if k < 0: return 0 freq: dict = defaultdict(int) left = count = 0 for right, x in enumerate(nums): freq[x] += 1 while len(freq) > k: freq[nums[left]] -= 1 if freq[nums[left]] == 0: del freq[nums[left]] left += 1 count += right - left + 1 return countfunction subarraysWithKDistinct(nums, k) { return atMost(nums, k) - atMost(nums, k - 1);}
function atMost(nums, k) { if (k < 0) return 0; const freq = new Map(); let left = 0, count = 0; for (let right = 0; right < nums.length; right++) { freq.set(nums[right], (freq.get(nums[right]) || 0) + 1); while (freq.size > k) { const v = freq.get(nums[left]) - 1; if (v === 0) freq.delete(nums[left]); else freq.set(nums[left], v); left++; } count += right - left + 1; // # of subarrays ending at right with ≤ k distinct } return count;}class Solution { public int subarraysWithKDistinct(int[] nums, int k) { return atMost(nums, k) - atMost(nums, k - 1); }
private int atMost(int[] nums, int k) { if (k < 0) return 0; Map<Integer, Integer> freq = new HashMap<>(); int left = 0, count = 0; for (int right = 0; right < nums.length; right++) { freq.merge(nums[right], 1, Integer::sum); while (freq.size() > k) { int v = freq.get(nums[left]) - 1; if (v == 0) freq.remove(nums[left]); else freq.put(nums[left], v); left++; } count += right - left + 1; // # of subarrays ending at right with <= k distinct } return count; }}function subarraysWithKDistinct(nums: number[], k: number): number { return atMost(nums, k) - atMost(nums, k - 1);}
function atMost(nums: number[], k: number): number { if (k < 0) return 0; const freq = new Map<number, number>(); let left = 0, count = 0; for (let right = 0; right < nums.length; right++) { freq.set(nums[right], (freq.get(nums[right]) || 0) + 1); while (freq.size > k) { const v = freq.get(nums[left])! - 1; if (v === 0) freq.delete(nums[left]); else freq.set(nums[left], v); left++; } count += right - left + 1; // # of subarrays ending at right with <= k distinct } return count;}Editorial#
The “count subarrays ending at right” trick is the elegance: for each right, the number of valid starting points is exactly right - left + 1. The exact-vs-at-most reduction is a workhorse for any “exactly K” counting problem — usually the at-most version has a clean sliding window while the exact version doesn’t.
Complexity#
- Time: O(n).
- Space: O(n).
Concept revision#
Related#