LeetCode 1481 - Least Number of Unique Integers after K Removals
This problem asks us to minimize the number of unique integers in an array after removing exactly k elements. In other words, we are given a list arr and a number k, and we need to strategically remove k elements so that the count of distinct integers left in the array is as…
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Greedy, Sorting, Counting
Solution
Problem Understanding
This problem asks us to minimize the number of unique integers in an array after removing exactly k elements. In other words, we are given a list arr and a number k, and we need to strategically remove k elements so that the count of distinct integers left in the array is as small as possible.
The input array arr contains integers that can be as large as 10^9 and can have up to 10^5 elements. This means the solution must be efficient in both time and space. The integer k represents exactly how many elements we must remove, so we cannot remove fewer or more.
The expected output is a single integer representing the minimum number of unique elements remaining in the array after the removals.
Important edge cases include scenarios where k is zero (no removal) or k equals the length of the array (all elements removed), and when multiple numbers have the same frequency, which could affect the removal strategy.
Approaches
Brute Force Approach
A naive approach would attempt to remove elements in all possible combinations to see which combination minimizes the number of unique integers. This could be implemented by generating all subsets of size k from the array, removing them, and counting the distinct integers in each result. This method is guaranteed to find the correct answer because it explores every possibility, but it is computationally infeasible. For an array of length n and k removals, the number of subsets to consider is C(n, k), which grows factorially with n and is far too large for n = 10^5.
Optimal Approach
The key insight for a better solution is frequency-based removal. We should remove integers that occur the fewest times first because removing them reduces the count of unique integers most efficiently. By counting the frequency of each integer and sorting them by frequency, we can greedily remove integers starting with the least frequent until k elements are removed. This approach is optimal because removing a higher-frequency integer first would consume more of k without reducing the number of unique integers as effectively.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(C(n, k) * n) | O(n) | Tries all combinations of removals; infeasible for large arrays |
| Optimal | O(n log n) | O(n) | Frequency counting and sorting, then greedy removal |
Algorithm Walkthrough
- Count Frequencies: Iterate through the array and create a hash map or dictionary to count how many times each integer appears. This allows us to understand which integers are cheap to remove.
- Sort by Frequency: Convert the frequency dictionary into a list of (number, frequency) pairs and sort it by frequency in ascending order. This ensures that we prioritize removing integers with the smallest frequency first.
- Greedy Removal: Initialize a counter for the number of unique integers. Iterate through the sorted frequency list, and for each integer, check if its frequency is less than or equal to the remaining
k. If so, remove it entirely, decrementkby its frequency, and reduce the unique counter. If the frequency is greater thank, removekelements from this integer and stop because we have used all removals. - Return the Result: After finishing the iteration or using all
kremovals, the remaining count of unique integers is the answer.
Why it works: The algorithm guarantees the minimum number of unique integers because removing integers in order of increasing frequency ensures that each unit of k contributes maximally to reducing the unique count. This greedy choice maintains an optimal invariant.
Python Solution
from typing import List
from collections import Counter
class Solution:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
# Step 1: Count frequencies
freq = Counter(arr)
# Step 2: Sort integers by frequency
freq_list = sorted(freq.values())
# Step 3: Greedy removal
unique_count = len(freq_list)
for f in freq_list:
if k >= f:
k -= f
unique_count -= 1
else:
break
return unique_count
This code first counts the frequency of each integer in arr using Counter, then sorts the frequencies. By iterating through the sorted list, we greedily remove the least frequent integers until k is exhausted, decrementing the count of unique integers each time. The remaining unique_count is returned.
Go Solution
import "sort"
func findLeastNumOfUniqueInts(arr []int, k int) int {
freq := make(map[int]int)
for _, num := range arr {
freq[num]++
}
freqList := make([]int, 0, len(freq))
for _, count := range freq {
freqList = append(freqList, count)
}
sort.Ints(freqList)
uniqueCount := len(freqList)
for _, f := range freqList {
if k >= f {
k -= f
uniqueCount--
} else {
break
}
}
return uniqueCount
}
In Go, we use a map to count frequencies and a slice to sort them. The logic is identical to the Python version, with attention to slice creation and map iteration.
Worked Examples
Example 1
Input: arr = [5,5,4], k = 1
| Step | Action | Remaining k | Unique Count | Notes |
|---|---|---|---|---|
| 1 | Count frequencies | - | - | 5:2, 4:1 |
| 2 | Sort by frequency | - | 2 | Sorted frequencies: [1,2] |
| 3 | Remove 4 (freq 1) | 0 | 1 | k used up, stop |
Output: 1
Example 2
Input: arr = [4,3,1,1,3,3,2], k = 3
| Step | Action | Remaining k | Unique Count | Notes |
|---|---|---|---|---|
| 1 | Count frequencies | - | - | 1:2, 2:1, 3:3, 4:1 |
| 2 | Sort by frequency | - | 4 | Sorted frequencies: [1,1,2,3] |
| 3 | Remove 2 (1) | 2 | 3 | k=3-1=2 |
| 4 | Remove 4 (1) | 1 | 2 | k=2-1=1 |
| 5 | Remove 1 (2) partially | 0 | 2 | Only 1 element removed, stop |
Output: 2
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Counting frequencies is O(n), sorting the frequency list is O(n log n), iteration is O(n) |
| Space | O(n) | Hash map and frequency list each take O(n) space |
The complexity is dominated by the sorting step. Frequency counting and iteration are linear, so the overall algorithm is efficient for the given constraints.
Test Cases
# Provided examples
assert Solution().findLeastNumOfUniqueInts([5,5,4], 1) == 1 # Single removal
assert Solution().findLeastNumOfUniqueInts([4,3,1,1,3,3,2], 3) == 2 # Multiple removals
# Boundary cases
assert Solution().findLeastNumOfUniqueInts([1], 0) == 1 # No removal
assert Solution().findLeastNumOfUniqueInts([1], 1) == 0 # Remove the only element
# Larger arrays
assert Solution().findLeastNumOfUniqueInts([1,2,3,4,5], 5) == 0 # Remove all
assert Solution().findLeastNumOfUniqueInts([1,2,2,3,3,3], 3) == 1 # Remove least frequent first
# Edge with equal frequencies
assert Solution().findLeastNumOfUniqueInts([1,1,2,2,3,3], 2) == 2 # Remove any two elements from lowest frequency
| Test | Why |
|---|---|
| [5,5,4], k=1 | Single removal, simple case |
| [4,3,1,1,3,3,2], k=3 | Mixed frequencies, multiple removals |
| [1], k=0 | No removal |
| [1], k=1 | All removed |
| [1,2,3,4,5], k=5 | Remove all elements |
| [1,2,2,3,3,3], k=3 | Greedy removal on uneven frequencies |
| [1,1,2,2,3,3], k=2 | Equal frequency handling |
Edge Cases
One important edge case is when k = 0. In this scenario, the algorithm should immediately return the total number of unique integers because no elements are removed. The Python and Go implementations handle this naturally, as the loop will never decrement k.
Another edge case occurs when k equals the length of the array. This means all elements must be removed, resulting in zero unique integers. Both implementations reduce `