Least Number of Unique Integers after K Removals
MedPrompt
Given an array of integers arr and an integer k. Find the least number of unique integers after removing exactly k elements.
Example 1:
Input: arr = [5,5,4], k = 1
Output: 1
Explanation: Remove the single 4, only 5 is left.Input: arr = [4,3,1,1,3,3,2], k = 3
Output: 2
Explanation: Remove 4, 2 and either one of the two 1s or three 3s. 1 and 3 will be left.
Constraints:
1 <= arr.length <= 10^51 <= arr[i] <= 10^90 <= k <= arr.length
Approaches
3 approaches with complexity analysis and trade-offs.
This approach first calculates the frequency of each number in the input array and then sorts these frequencies. By sorting the frequencies in ascending order, we can greedily remove the numbers that appear least often to minimize the number of unique integers.
Algorithm
- Create a
HashMapto store the frequency of each integer inarr. - Create a
Listand populate it with the frequency values from theHashMap. - Sort the list of frequencies in non-decreasing order.
- Initialize
uniqueCountto the initial number of unique integers (the size of the list). - Iterate through the sorted frequencies. For each frequency
f:- If
kis greater than or equal tof, subtractffromkand decrementuniqueCount. - Otherwise, break the loop.
- If
- Return
uniqueCount.
Walkthrough
The core idea is that to reduce the count of unique integers most effectively, we should remove elements that form the smallest frequency groups.
- Frequency Counting: We traverse the input array
arrand use aHashMapto store the count of each integer. The keys of the map will be the unique numbers, and the values will be their frequencies. - Extract and Sort Frequencies: We extract all the frequency values from the
HashMapinto a list. Then, we sort this list in ascending order. This places the counts of the rarest elements at the beginning of the list. - Greedy Removal: We initialize a variable
uniqueCountto the total number of unique elements (the size of the map). We then iterate through the sorted frequencies. For each frequencyf, we check if we have enoughkremovals left to remove all occurrences of an element with that frequency.- If
k >= f, we perform the removal by subtractingffromkand decrementinguniqueCount. - If
k < f, we don't have enough removals to eliminate this entire group of numbers (or any subsequent, larger groups). We stop the process here.
- If
- Result: The final value of
uniqueCountis the minimum number of unique integers remaining.
import java.util.*; class Solution { public int findLeastNumOfUniqueInts(int[] arr, int k) { // 1. Count frequencies of each number Map<Integer, Integer> freqMap = new HashMap<>(); for (int num : arr) { freqMap.put(num, freqMap.getOrDefault(num, 0) + 1); } // 2. Extract frequencies into a list List<Integer> frequencies = new ArrayList<>(freqMap.values()); // 3. Sort the frequencies in ascending order Collections.sort(frequencies); // 4. Iterate and remove elements int uniqueCount = frequencies.size(); for (int freq : frequencies) { if (k >= freq) { k -= freq; uniqueCount--; } else { // Not enough k to remove this entire group break; } } return uniqueCount; }}Complexity
Time
O(N + U log U), where N is the number of elements in `arr` and U is the number of unique elements. O(N) to build the frequency map. O(U log U) to sort the frequencies. O(U) to iterate through the frequencies. The sorting step dominates.
Space
O(U), for the `HashMap` and the list of frequencies, where U is the number of unique elements. In the worst case, where all elements are unique, this becomes O(N).
Trade-offs
Pros
Relatively simple to understand and implement.
Works correctly for all cases.
Cons
The sorting step (O(U log U)) is not the most optimal, especially when the number of unique elements U is large.
This approach improves upon sorting by using a Min-Heap (Priority Queue) to efficiently access the smallest frequency at each step. Instead of sorting all frequencies at once, we process them one by one, always picking the smallest available.
Algorithm
- Create a
HashMapto store the frequency of each integer inarr. - Create a
PriorityQueue(Min-Heap) and add all the frequency values from theHashMapto it. - While
k > 0and the heap is not empty:- Get the smallest frequency
ffrom the heap's top. - If
kis greater than or equal tof, subtractffromkand remove the element from the heap. - Otherwise, break the loop.
- Get the smallest frequency
- Return the final size of the heap.
Walkthrough
Similar to the sorting approach, we first need to count the frequencies. However, instead of sorting them all, we use a data structure that keeps them ordered dynamically.
- Frequency Counting: Use a
HashMapto count the occurrences of each number inarr. - Build Min-Heap: Create a
PriorityQueue(which acts as a Min-Heap in Java) and insert all the frequency values from theHashMapinto it. The heap will automatically maintain the smallest frequency at its root. - Greedy Removal: While
kis greater than 0 and the heap is not empty, we repeatedly check the smallest frequency (the top of the heap).- If
kis greater than or equal to the smallest frequency, we can remove that group of elements. Wepollthe frequency from the heap and subtract its value fromk. - If
kis smaller than the smallest frequency, we cannot remove any more full groups, so we stop.
- If
- Result: The number of elements remaining in the heap is the least number of unique integers after the removals.
import java.util.*; class Solution { public int findLeastNumOfUniqueInts(int[] arr, int k) { // 1. Count frequencies of each number Map<Integer, Integer> freqMap = new HashMap<>(); for (int num : arr) { freqMap.put(num, freqMap.getOrDefault(num, 0) + 1); } // 2. Build a Min-Heap of frequencies PriorityQueue<Integer> minHeap = new PriorityQueue<>(freqMap.values()); // 3. Greedily remove from the heap while (k > 0 && !minHeap.isEmpty()) { int smallestFreq = minHeap.peek(); if (k >= smallestFreq) { k -= smallestFreq; minHeap.poll(); } else { break; } } // 4. The size of the heap is the number of unique integers left return minHeap.size(); }}Complexity
Time
O(N + U log U), where N is the length of `arr` and U is the number of unique elements. O(N) for frequency counting. O(U log U) to build the heap by inserting U elements. O(k' log U) for removals, where k' is the number of groups removed. In the worst case, this is O(U log U). The total complexity is dominated by building and processing the heap.
Space
O(U), for the `HashMap` and the `PriorityQueue`, where U is the number of unique elements. In the worst case, O(N).
Trade-offs
Pros
Conceptually clean, always processing the next smallest element.
Can be more efficient than full sorting if
kis very small, as we don't need to process the entire heap.
Cons
Has the same worst-case time complexity as the sorting approach.
Slightly more overhead due to the heap data structure compared to a simple array/list sort.
This is the most optimal approach, achieving linear time complexity. It leverages the fact that frequencies are integers within a known range. Instead of a comparison-based sort, we use a form of counting sort (or bucket sort) to group numbers by their frequency.
Algorithm
- Create a
HashMapto store the frequency of each integer inarr. - Initialize
uniqueCountto the size of theHashMap. - Create a
bucketsarray of sizearr.length + 1. - Iterate through the frequencies in the
HashMap. For each frequencyf, incrementbuckets[f]. - Iterate through the
bucketsarray from frequencyi = 1toarr.length. - Let
countOfNumbersbebuckets[i]. If it's greater than 0:- Calculate the total elements to remove for this frequency:
cost = i * countOfNumbers. - If
k >= cost, subtractcostfromkandcountOfNumbersfromuniqueCount. - Otherwise, calculate how many full groups can be removed:
groupsToRemove = k / i. SubtractgroupsToRemovefromuniqueCountand return the result.
- Calculate the total elements to remove for this frequency:
- If the loop completes, return
uniqueCount(which would be 0 if k was large enough).
Walkthrough
This method avoids the O(U log U) sorting bottleneck by using an array to act as buckets for frequencies.
- Frequency Counting: As before, use a
HashMapto count the frequency of each number inarr. - Bucket Frequencies: Create an array,
buckets, of sizearr.length + 1.buckets[i]will store the count of numbers that have a frequency ofi. We iterate through the frequencies in ourHashMapand for each frequencyf, we incrementbuckets[f]. - Greedy Removal from Buckets: We iterate through the
bucketsarray from the smallest frequencyi = 1upwards. LetuniqueCountbe the initial number of unique elements.- For each frequency
i, we know there arebuckets[i]unique numbers that appearitimes. - If we have enough
kto remove all these groups (i.e.,k >= i * buckets[i]), we do so by updatingkand decrementinguniqueCountbybuckets[i]. - If we don't have enough
k, we calculate how many full groups we can remove (k / i), subtract that fromuniqueCount, and return immediately, as we can't remove any more groups.
- For each frequency
- Result: The final
uniqueCountis the answer.
import java.util.*; class Solution { public int findLeastNumOfUniqueInts(int[] arr, int k) { // 1. Count frequencies of each number Map<Integer, Integer> freqMap = new HashMap<>(); for (int num : arr) { freqMap.put(num, freqMap.getOrDefault(num, 0) + 1); } int uniqueCount = freqMap.size(); // 2. Bucket the frequencies. buckets[i] = count of numbers with frequency i int[] buckets = new int[arr.length + 1]; for (int freq : freqMap.values()) { buckets[freq]++; } // 3. Iterate through buckets and remove for (int i = 1; i < buckets.length; i++) { // i is the frequency // buckets[i] is the number of elements with this frequency if (buckets[i] > 0) { int numElementsToRemove = i * buckets[i]; if (k >= numElementsToRemove) { k -= numElementsToRemove; uniqueCount -= buckets[i]; } else { // Cannot remove all elements with this frequency. // Remove as many full groups as possible. int numGroupsToRemove = k / i; uniqueCount -= numGroupsToRemove; // We have used up our k, so we are done. return uniqueCount; } } } return uniqueCount; }}Complexity
Time
O(N), where N is the length of `arr`. O(N) to build the frequency map. O(U) to populate the buckets, where U is the number of unique elements (U <= N). O(N) to iterate through the buckets array. The overall complexity is linear.
Space
O(N), for the `HashMap` (up to O(U)) and the `buckets` array (size N+1). In the worst case, O(N).
Trade-offs
Pros
Most efficient solution with linear time complexity.
Avoids comparison-based sorting.
Cons
Requires extra space proportional to N for the bucket array, which might be large if N is large, even if the number of unique elements U is small.
Solutions
Solution
class Solution { public int findLeastNumOfUniqueInts ( int [] arr , int k ) { Map < Integer , Integer > cnt = new HashMap <>(); for ( int x : arr ) { cnt . merge ( x , 1 , Integer: : sum ); } List < Integer > nums = new ArrayList <>( cnt . values ()); Collections . sort ( nums ); for ( int i = 0 , m = nums . size (); i < m ; ++ i ) { k -= nums . get ( i ); if ( k < 0 ) { return m - i ; } } return 0 ; } }class Solution { public: int findLeastNumOfUniqueInts ( vector < int >& arr , int k ) { unordered_map < int , int > cnt ; for ( int & x : arr ) { ++ cnt [ x ]; } vector < int > nums ; for ( auto & [ _ , c ] : cnt ) { nums . push_back ( c ); } sort ( nums . begin (), nums . end ()); for ( int i = 0 , m = nums . size (); i < m ; ++ i ) { k -= nums [ i ]; if ( k < 0 ) { return m - i ; } } return 0 ; } };class Solution : def findLeastNumOfUniqueInts ( self , arr : List [ int ], k : int ) -> int : cnt = Counter ( arr ) for i , v in enumerate ( sorted ( cnt . values ())): k -= v if k < 0 : return len ( cnt ) - i return 0Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.