1481. Least Number of Unique Integers after K Removals
Problem Description
You are given an array of integers arr
and an integer k
. Your task is to remove exactly k
elements from the array such that the number of unique integers remaining in the array is minimized. Return the least number of unique integers after removing exactly k
elements.
For example, if arr = [5, 5, 4]
and k = 1
, you can remove one element. If you remove one 5
, you'll have [5, 4]
with 2 unique integers. If you remove the 4
, you'll have [5, 5]
with only 1 unique integer. So the answer would be 1.
The strategy is to remove integers that appear less frequently first, as this will reduce the number of unique integers more efficiently. By removing all occurrences of integers with smaller frequencies first, you can minimize the total number of unique integers remaining.
Intuition
To minimize the number of unique integers after removing k
elements, we need to think about which elements to remove first. The key insight is that we should prioritize removing integers that appear less frequently in the array.
Why? Consider this: if an integer appears only once, removing that single element eliminates one unique integer entirely. If an integer appears 5 times, we'd need to remove all 5 occurrences to eliminate that unique integer. So with limited removals (k
), it's more efficient to target integers with lower frequencies.
The greedy approach works here: sort the integers by their frequency counts in ascending order, then remove them starting from the least frequent ones. For each unique integer, we remove all its occurrences. We keep removing until we've used up our k
removals.
For example, if we have frequencies [1, 1, 2, 3, 4]
and k = 4
, we can remove the integers with frequencies 1 and 1 (using 2 removals), then the integer with frequency 2 (using 2 more removals, totaling 4). This eliminates 3 unique integers, leaving us with 2 unique integers remaining.
By counting frequencies first, sorting them, and then greedily removing from smallest to largest frequency, we ensure we're eliminating the maximum number of unique integers possible with our k
removals.
Solution Approach
The implementation follows these steps:
-
Count frequencies: Use a hash table (
Counter
in Python) to count how many times each integer appears in the array. This gives us a mapping from each unique integer to its frequency. -
Sort frequencies: Extract just the frequency values from the hash table and sort them in ascending order. We don't need to track which integer has which frequency - we only care about the frequency values themselves.
-
Greedy removal: Iterate through the sorted frequencies from smallest to largest. For each frequency value
v
:- Subtract
v
fromk
(representing the removal of all occurrences of that integer) - If
k
becomes negative, it means we couldn't fully remove this integer with our remaining removals. The number of unique integers left is the total number of unique integers minus the number we've successfully removed (which is the current indexi
) - If
k
is still non-negative, continue to the next frequency
- Subtract
-
Return result: If we successfully process all frequencies without
k
going negative, it means we've removed all unique integers, so return0
.
The algorithm works because:
- By processing frequencies in ascending order, we maximize the number of unique integers we can completely remove
- We keep track of our progress using the index
i
in the sorted frequency list - When
k
goes negative, we know exactly how many unique integers we've eliminated (i
) and can calculate how many remain (len(cnt) - i
)
Time complexity: O(n log n)
for sorting the frequencies, where n
is the number of unique integers.
Space complexity: O(n)
for the hash table storing the frequencies.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with arr = [4, 3, 1, 1, 3, 3, 2]
and k = 3
.
Step 1: Count frequencies
- Integer 1 appears 2 times
- Integer 2 appears 1 time
- Integer 3 appears 3 times
- Integer 4 appears 1 time
So our frequency map is: {1: 2, 2: 1, 3: 3, 4: 1}
Step 2: Sort frequencies
Extract the frequency values: [2, 1, 3, 1]
Sort them in ascending order: [1, 1, 2, 3]
Step 3: Greedy removal
We have k = 3
removals available. Process frequencies from smallest to largest:
-
Index 0: frequency = 1
- Remove this integer completely (uses 1 removal)
k = 3 - 1 = 2
(still positive, continue)
-
Index 1: frequency = 1
- Remove this integer completely (uses 1 removal)
k = 2 - 1 = 1
(still positive, continue)
-
Index 2: frequency = 2
- Try to remove this integer completely (would use 2 removals)
k = 1 - 2 = -1
(negative! Can't fully remove this integer)- Stop here
Step 4: Calculate result
- Total unique integers originally: 4
- Successfully removed integers: 2 (at indices 0 and 1)
- Remaining unique integers:
4 - 2 = 2
The answer is 2. We removed the two integers that appeared once (integers 2 and 4), leaving us with integers 1 and 3 in the array.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
6 # Count frequency of each element in the array
7 frequency_map = Counter(arr)
8
9 # Sort frequencies in ascending order to remove least frequent elements first
10 sorted_frequencies = sorted(frequency_map.values())
11
12 # Iterate through sorted frequencies
13 for index, frequency in enumerate(sorted_frequencies):
14 # Subtract current frequency from k (simulate removing all occurrences)
15 k -= frequency
16
17 # If k becomes negative, we can't remove this element completely
18 if k < 0:
19 # Return remaining unique integers
20 # (total unique - number of completely removed elements)
21 return len(frequency_map) - index
22
23 # If we removed all elements (k >= sum of all frequencies)
24 return 0
25
1class Solution {
2 public int findLeastNumOfUniqueInts(int[] arr, int k) {
3 // Create a frequency map to count occurrences of each element
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5
6 // Count the frequency of each element in the array
7 for (int element : arr) {
8 frequencyMap.merge(element, 1, Integer::sum);
9 }
10
11 // Extract all frequency values into a list
12 List<Integer> frequencies = new ArrayList<>(frequencyMap.values());
13
14 // Sort frequencies in ascending order to remove elements with lowest frequencies first
15 Collections.sort(frequencies);
16
17 // Iterate through sorted frequencies and remove elements
18 int totalUniqueElements = frequencies.size();
19 for (int i = 0; i < totalUniqueElements; i++) {
20 // Subtract current frequency from k (remove all occurrences of current element)
21 k -= frequencies.get(i);
22
23 // If k becomes negative, we can't remove the current element completely
24 // Return the number of remaining unique elements
25 if (k < 0) {
26 return totalUniqueElements - i;
27 }
28 }
29
30 // All unique elements were removed, return 0
31 return 0;
32 }
33}
34
1class Solution {
2public:
3 int findLeastNumOfUniqueInts(vector<int>& arr, int k) {
4 // Count frequency of each element in the array
5 unordered_map<int, int> frequencyMap;
6 for (int& element : arr) {
7 ++frequencyMap[element];
8 }
9
10 // Extract all frequencies into a vector
11 vector<int> frequencies;
12 for (auto& [value, frequency] : frequencyMap) {
13 frequencies.push_back(frequency);
14 }
15
16 // Sort frequencies in ascending order
17 // Strategy: Remove elements with lowest frequency first to maximize unique elements removed
18 sort(frequencies.begin(), frequencies.end());
19
20 // Greedily remove elements starting from lowest frequency
21 int uniqueCount = frequencies.size();
22 for (int i = 0; i < uniqueCount; ++i) {
23 k -= frequencies[i];
24
25 // If we can't remove all occurrences of current element
26 // Return remaining unique elements count
27 if (k < 0) {
28 return uniqueCount - i;
29 }
30 }
31
32 // All unique elements were removed
33 return 0;
34 }
35};
36
1/**
2 * Finds the least number of unique integers after removing exactly k elements
3 * @param arr - The input array of integers
4 * @param k - The number of elements to remove
5 * @returns The minimum number of unique integers remaining
6 */
7function findLeastNumOfUniqueInts(arr: number[], k: number): number {
8 // Create a frequency map to count occurrences of each unique integer
9 const frequencyMap: Map<number, number> = new Map<number, number>();
10
11 // Count the frequency of each integer in the array
12 for (const num of arr) {
13 frequencyMap.set(num, (frequencyMap.get(num) ?? 0) + 1);
14 }
15
16 // Extract all frequencies and sort them in ascending order
17 // Strategy: Remove integers with lowest frequencies first to minimize unique count
18 const frequencies: number[] = [...frequencyMap.values()].sort((a, b) => a - b);
19
20 // Iterate through sorted frequencies and remove integers starting from lowest frequency
21 for (let i = 0; i < frequencies.length; i++) {
22 // Subtract current frequency from remaining removals
23 k -= frequencies[i];
24
25 // If we can't completely remove this integer, return remaining unique count
26 if (k < 0) {
27 return frequencies.length - i;
28 }
29 }
30
31 // All unique integers were removed
32 return 0;
33}
34
Time and Space Complexity
The time complexity is O(n log n)
, where n
is the length of the array arr
. This is because:
- Creating the Counter takes
O(n)
time to count all elements - Getting the values from Counter takes
O(m)
time wherem
is the number of unique elements (at mostn
) - Sorting the frequency values takes
O(m log m)
time, which is at mostO(n log n)
- The enumeration loop iterates through at most
m
values, takingO(m)
time - Overall:
O(n) + O(m log m) + O(m) = O(n log n)
sincem โค n
The space complexity is O(n)
, where n
is the length of the array arr
. This is because:
- The Counter dictionary stores at most
n
key-value pairs (when all elements are unique) - The sorted list of values creates a new list of size at most
n
- Overall:
O(n) + O(n) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Attempting to Track Which Elements to Remove
A common mistake is trying to maintain which specific elements are being removed from the array, rather than just working with frequencies. This leads to unnecessary complexity.
Incorrect approach:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
frequency_map = Counter(arr)
# Trying to sort by value AND track the keys
sorted_items = sorted(frequency_map.items(), key=lambda x: x[1])
removed_elements = set()
for value, freq in sorted_items:
if k >= freq:
k -= freq
removed_elements.add(value) # Unnecessary tracking
else:
break
return len(frequency_map) - len(removed_elements)
Why it's problematic: We don't need to know which specific integers we're removing - we only need to count how many unique integers remain.
Pitfall 2: Off-by-One Error in Counting Remaining Elements
Another frequent error occurs when k
becomes negative, leading to incorrect calculation of remaining unique integers.
Incorrect approach:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
frequency_map = Counter(arr)
sorted_frequencies = sorted(frequency_map.values())
for index, frequency in enumerate(sorted_frequencies):
k -= frequency
if k < 0:
# Wrong! This counts the current element as removed
return len(frequency_map) - index - 1
return 0
Why it's problematic: When k
goes negative, it means we couldn't fully remove the element at the current index. The number of completely removed elements is exactly index
, not index + 1
.
Pitfall 3: Not Handling Edge Cases Properly
Failing to consider when k
equals 0 or when k
is greater than or equal to the array length.
Incorrect approach:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
if k == 0: # Unnecessary special case
return len(set(arr))
frequency_map = Counter(arr)
sorted_frequencies = sorted(frequency_map.values())
# Missing check for k >= len(arr)
for index, frequency in enumerate(sorted_frequencies):
k -= frequency
if k < 0:
return len(frequency_map) - index
# May not reach here if logic is wrong
Solution: The original code handles these cases naturally without special checks. When k = 0
, the first frequency subtraction will make k
negative, returning the full count. When k >= len(arr)
, all frequencies will be processed and the function returns 0.
Which of the following is a min heap?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!