SORTING › COUNTING SORT
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →Deleting one distinct number removes all of its occurrences, so every possible choice has a simple value: its frequency. The actual integer does not matter. If one number appears 20 times and another appears twice, choosing the first removes far more array elements while increasing the set size by exactly one.
The target is half of the original array, and every chosen number costs one slot in the set. To reach that target with as few slots as possible, take the largest available frequencies first. After sorting all frequencies from largest to smallest, the first prefix whose sum reaches half gives the minimum answer: replacing one of those choices with a smaller frequency could only reduce the number removed.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n + m log m) | The hash-map pass processes each of the n elements once. Building the frequency vector touches each of the m distinct keys, and sorting those m counts costs O(m log m); the final scan visits each count at most once. |
| Space | O(m) extra | The frequency map and the vector of counts hold one entry per distinct value. The returned set is not materialized, so there is no output storage to count; in the worst case m equals n, making the extra space O(n). |
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
int minSetSize(vector<int>& arr) {
unordered_map<int, int> freq;
for (int x : arr) {
freq[x]++;
}
vector<int> counts;
for (const auto& entry : freq) {
counts.push_back(entry.second);
}
sort(counts.rbegin(), counts.rend());
int removed = 0;
int half = static_cast<int>(arr.size()) / 2;
int answer = 0;
for (int count : counts) {
removed += count;
answer++;
if (removed >= half) {
break;
}
}
return answer;
}
};The important placement is the stopping check after adding a frequency. That frequency belongs to the chosen set, even if it carries removed past the target, so answer must increase before the check. Using >= rather than > also handles the exact-half case without selecting an unnecessary extra value.
Because every value is between 1 and 100000, you can replace the hash map with a direct frequency array. After counting values, group those frequencies by their size: bucket f tells you how many distinct values occur exactly f times. Reading the buckets from n down to 1 is counting sort over the frequencies, so this avoids the m log m comparison sort.
#include <vector>
using namespace std;
class Solution {
public:
int minSetSize(vector<int>& arr) {
const int maxValue = 100000;
vector<int> valueFrequency(maxValue + 1, 0);
for (int x : arr) {
valueFrequency[x]++;
}
vector<int> frequencyBucket(arr.size() + 1, 0);
for (int x = 1; x <= maxValue; x++) {
if (valueFrequency[x] > 0) {
frequencyBucket[valueFrequency[x]]++;
}
}
int half = static_cast<int>(arr.size()) / 2;
int removed = 0;
int answer = 0;
for (int frequency = static_cast<int>(arr.size()); frequency >= 1; frequency--) {
while (frequencyBucket[frequency] > 0 && removed < half) {
removed += frequency;
answer++;
frequencyBucket[frequency]--;
}
if (removed >= half) {
break;
}
}
return answer;
}
};This version is an optimization only when the value range is known and small enough to allocate. Its time is O(n + V), where V is the maximum possible value, and its extra space is O(n + V). The original map-and-sort version is more general because it does not depend on that range; it is usually the clearer choice when values can be arbitrary.