Least Number of Unique Integers after K Removals

Medium Greedy

Problem

Given an integer array arr and an integer k, remove exactly k elements from the array such that the number of unique integers remaining is minimized, and return this minimum number of unique integers.

  • 1 ≤ arr.length ≤ 10⁵
  • 1 ≤ arr[i] ≤ 10⁹
  • 1 ≤ k ≤ arr.length

Example

Input: arr = [5,5,4], k = 1
Output: 1

The frequency counts are {5: 2, 4: 1}. Removing one element from the integer with the smallest frequency (4) removes it entirely, leaving only one unique integer (5). This is the minimal number of unique integers achievable after removing k=1 elements.

Approach

Straightforward Solution

A brute-force approach would try all combinations of removing k elements, which is combinatorially explosive and infeasible for large arrays.

Core Observation

The minimal number of unique integers after removing k elements is achieved by removing entire integers with the smallest frequencies first, because removing all occurrences of a low-frequency integer reduces the unique count by one at the least cost.

Path to Optimal

The key insight is to count the frequencies of each integer, then sort these frequencies in ascending order. Removing integers with the smallest frequencies first uses the fewest removals to reduce the unique count. Iteratively subtract frequencies from k until k is exhausted or no more integers can be fully removed. The remaining unique integers are those not fully removed.

Optimal Approach

Count frequencies using a hash map, sort the frequency values ascendingly, then greedily remove integers starting from the smallest frequency by subtracting their counts from k. Stop when k is less than the next frequency. The answer is the total unique integers minus the count of fully removed integers.

Time

O(n log n)

Counting frequencies takes O(n). Sorting the frequency list takes O(m log m), where m is the number of unique integers, which is at most n. The while loop iterates at most m times, so total complexity is dominated by sorting.

Space

O(n)

The frequency map stores counts for up to n unique integers in the worst case, requiring O(n) auxiliary space.

Pattern Spotlight

Greedy Algorithms (Frequency-Based Removal)

When minimizing the count of unique elements after removals, always remove entire elements with the smallest frequencies first, as this yields the greatest reduction in unique count per removal cost.

Solution

1from collections import Counter
2
3class Solution:
4 def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
5 counts = Counter(arr)
6 freqs = sorted(counts.values())
7
8 i = 0
9 while i < len(freqs) and k >= freqs[i]:
10 k -= freqs[i]
11 i += 1
12
13 return len(freqs) - i

Step-by-Step Solution

1

Count Frequencies of Each Integer in the Array

5counts = Counter(arr)

Objective

To build a frequency map that records how many times each integer appears in the array.

Key Insight

Knowing the frequency of each integer is essential because the cost to remove an integer entirely equals its frequency. This frequency map enables the greedy strategy to prioritize removals of integers with the smallest counts first.

Interview Quick-Check

Core Logic

Counting frequencies transforms the problem from element-level removals to frequency-level removals, enabling efficient greedy decisions.

Complexity

Frequency counting is O(n) time and O(n) space, where n is the array length.

2

Sort Frequencies to Prioritize Removal of Least Frequent Integers

6freqs = sorted(counts.values())

Objective

To order the frequency counts in ascending order so that integers with the smallest frequencies can be removed first.

Key Insight

Sorting frequencies allows the algorithm to greedily remove entire integers starting from those that require the fewest removals, maximizing the reduction in unique integers per unit of k spent.

Interview Quick-Check

Core Logic

Sorting frequencies enables a linear scan to remove integers in order of increasing cost, which is the essence of the greedy approach.

Complexity

Sorting takes O(m log m) time, where m is the number of unique integers.

Common Pitfalls & Bugs

A common mistake is sorting the integers themselves instead of their frequencies. The greedy order depends only on how many times an integer appears, not on its numeric value.

3

Greedily Remove Integers Starting from the Smallest Frequency

8i = 0
9while i < len(freqs) and k >= freqs[i]:
10 k -= freqs[i]
11 i += 1

Objective

To iteratively subtract frequencies from k, removing entire integers until k is exhausted or no more integers can be fully removed.

Key Insight

By removing integers with the smallest frequencies first, the algorithm uses the limited removal budget k most efficiently to reduce the unique count. The loop stops when k is insufficient to remove the next integer entirely, ensuring no partial removals that don't reduce unique count.

Interview Quick-Check

Core Logic

The loop removes entire integers by subtracting their frequency from k, counting how many unique integers are fully removed.

State & Boundaries

The loop condition ensures that only integers whose frequency is less than or equal to the remaining k are removed.

Common Pitfalls & Bugs

Failing to check k >= freqs[i] before removal can lead to incorrect partial removals that don't reduce unique count.

4

Calculate and Return the Remaining Number of Unique Integers

13return len(freqs) - i

Objective

To compute the final count of unique integers remaining after removals and return it.

Key Insight

The remaining unique integers equal the total unique integers minus the count of fully removed integers. This calculation directly reflects the problem's goal.

Interview Quick-Check

Core Logic

Subtracting the count of fully removed integers from the total unique count yields the minimal unique integers after k removals.

Line Analysis

This solution has 1 Critical line interviewers watch for.

Line 9 Critical
while i < len(freqs) and k >= freqs[i]:

Check if the current integer's frequency can be fully removed within the remaining k.

This conditional check is critical because removing an integer only reduces the unique count if all its occurrences are removed; partial removals do not reduce the unique count and would violate the problem constraints.

Line 5 Core
counts = Counter(arr)

Count the frequency of each integer in the array using a hash map.

This frequency map is the foundation for the greedy strategy, enabling the algorithm to prioritize removals based on integer occurrence counts.

Line 6 Core
freqs = sorted(counts.values())

Sort the frequency values in ascending order.

Sorting frequencies allows the algorithm to efficiently remove integers starting from those with the smallest counts, maximizing unique integer reduction per removal.

Line 13 Core
return len(freqs) - i

Return the number of unique integers remaining after removals.

Subtracting the count of fully removed integers from the total unique count yields the minimal unique integers achievable after k removals, which is the problem's required output.

Line 10 Core
k -= freqs[i]

Subtract the current integer's frequency from k to account for removals.

This updates the remaining removal budget, reflecting the cost of removing the entire integer.

Line 11 Standard
i += 1

Increment the count of fully removed integers.

Tracking the number of fully removed integers is essential to compute the final number of unique integers remaining.

Line 8 Standard
i = 0

Initialize the index to track how many integers have been fully removed.

This index counts how many integers have been removed entirely, which will be used to calculate the final answer.

Test Your Understanding

Why does removing integers with the smallest frequencies first minimize the number of unique integers remaining?

Because removing all occurrences of a low-frequency integer costs fewer removals (k) to reduce the unique count by one, maximizing the reduction in unique integers per removal.

Related Problems

Greedy pattern

Don't just read it. Drill it.

Reconstruct Least Number of Unique Integers after K Removals from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Least Number of Unique Integers after K Removals drill

or drill a free problem