Top K Frequent Elements
Given an integer array numskk
Example 1
Output: [1,2]
Explanation: '1' appeared three times and '2' appeared two times, they are the top 2 frequent elements.
Example 2
Output: [1]
Follow up:
Your algorithm's time complexity must be better than O(n log n), where n is the array's size. (Solution 3 and 4 below runs with O(n) time complexity)Contents
Solution 1 - using sorting, find number of times each element appeared and then sort them by that count Solution 2 - using Min Heap, find number of times each element appeared and then add to min heap with at most 'k' elements Solution 3 - using QuickSelect algorithm Solution 4 - using a technique used in BucketSort (easy and most efficient solution)
Solution 1 - using sorting, find number of times each element appeared and then sort them by that count
In this approach, we are going to loop through numbers in numskeyvalueHashMap
Then, copy the entries of HashMapList
After the entries are sorted, take the last k
Consider an example with nums[1, 1, 1, 2, 3, 2, 1]k = 2
-
In the first step, we will initialize a
and store the count of each number to number of times it appeared.HashMap Now, theJavaHashMap<Integer, Integer> map = new HashMap<>(); IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum)); will looks like this.HashMap appeared 4 times,1 appeared 2 times and2 appeared 1 time.3Mapping key value 1 4 2 2 3 1 -
Now, take these entries into a
and sort them by count of the number.ArrayList After sorting, it looks like this.CodeArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet()); list.sort(Comparator.comparingInt(Map.Entry::getValue));List after sorting entry key entry value 3 1 2 2 1 4 -
Now, we will return the last two eleemnts from this sorted list. i.e.
or[2, 1] in any order.[1, 2]
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
public class TopKFrequentElements {
/**
* First, store the mapping of number -> number of times it appeared into a HashMap
* Second, sort the entries of HashMap using the values (number of times a number is appeared)
* Then, take last K elements
*/
static int[] usingSorting(int[] nums, int k) {
int[] ans = new int[k];
HashMap<Integer, Integer> map = new HashMap<>();
IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum));
ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
list.sort(Comparator.comparingInt(Map.Entry::getValue));
for(int i=0; i<k; i++) {
ans[i] = list.get(list.size()-1-i).getKey();
}
return ans;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n log(n)) time where nnums
Space complexity: O(n).
Solution 2 - using Min Heap, find number of times each element appeared and then add to min heap with at most 'k' elements
In this approach, we are going to loop through numbers in numskeyvalueHashMap
In the next step we are going to create a PriorityQueue
Then in the next step, we will loop through HashMapPriorityQueuePriorityQueuekkPriorityQueuek
Finally return the elements from the PriorityQueue
Consider an example with nums[1, 1, 1, 2, 3, 2, 1]k = 2
-
In the first step, we will initialize a
and store the count of each number to number of times it appeared.HashMap Now, theJavaHashMap<Integer, Integer> map = new HashMap<>(); IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum)); will looks like this.HashMap appeared 4 times,1 appeared 2 times and2 appeared 1 time.3Mapping key value 1 4 2 2 3 1 -
Now we will initialize
which will usePriorityQueue entry's value when it needs to orders elements inside Heap.HashMapJavaPriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>(k, Comparator.comparingInt(Map.Entry::getValue)); -
Now, then loop through
entries, add them to theHashMap we created above and whenever it reachesPriorityQueue size then remove the top element, so that it keeps onlyk+1 elements.k At the endCodemap.entrySet().forEach(entry->{ minHeap.offer(entry); if(minHeap.size() >k) { minHeap.poll(); } }); , it looks like this.PriorityQueueList after sorting entry key entry value 1 4 2 2 -
Now, we will return the eleemnts from this
, which will bePriorityQueue[1, 2]
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
public class TopKFrequentElements {
/**
*
* First, store the mapping of number -> number of times it appeared into a HashMap
* Next, Crate a Min Heap (default Java Priority Queue is Min heap, i.e. at top it keeps min element and children are greater than parent)
* Then, loop through entries of HashMap and add it to PriorityQueue, if it is reaches size 'k' then poll, by the end will have top k elements
*/
static int[] usingHeap(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum));
final PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>(k, Comparator.comparingInt(Map.Entry::getValue));
map.entrySet().forEach(entry->{
minHeap.offer(entry);
if(minHeap.size() >k) {
minHeap.poll();
}
});
int[] result = new int[k];
for(int i=0;i<k; i++) {
result[i] = minHeap.poll().getKey();
}
return result;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n log(k)) time where nnumsPriorityQueueheapify()
Space complexity: O(n).
Solution 3 - using QuickSelect algorithm
In this approach, we are apply
We will be leveraging the same solution here. The main advantage with this solution is that, initially we will be selecting a random indexed element
from the input array and call it pivotpivotpivotpivot
Now we can draw a decision based on where pivot
-
If
andk element's index are same, that means we have found our answer. All elements on the right side of pivot element are the top K frequent elements.pivot -
If
is less thank element's index, that means we have left some elements on the left side of the array to cover top K frequent elements. Now, we update our scope of algorithm topivot index to0th 's index.pivot -
If
is greater thank element's index, that means we have extra elements on the right side of the array to cover top K frequent elements. Now, we update our scope of algorithm topivot 's indexpivot index.right
pivot
This algorithm runs with average time complexity of O(n)O(n2)
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
public class TopKFrequentElements {
/**
* Using QuickSelect algorithm
*/
static int[] usingQuickSelect(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum));
Map.Entry<Integer, Integer>[] entries= map.entrySet().toArray(new Map.Entry[0]);
// we are passing k as entries.length - k, because we need last k elements
selectTopKFrequent(entries, 0, entries.length-1, entries.length-k); //entries wil be updated by reference
int[] result = new int[k];
for(int i=0; i<k; i++) { // take last k elements from array
result[i] = entries[entries.length-1-i].getKey();
}
return result;
}
static void selectTopKFrequent(Map.Entry<Integer, Integer>[] entries, int left, int right, int k) {
if(left == right) {
return;
}
int storeIndex = partition(entries, left, right);
if(storeIndex == k) {
//k is positioned at the right place
return;
} else if (k< storeIndex) { // if K is smaller than current pivot, move to the left
selectTopKFrequent(entries, left, storeIndex -1, k);
} else {
selectTopKFrequent(entries, storeIndex+1, right, k);
}
}
static int partition(Map.Entry<Integer, Integer>[] entries, int left, int right) {
Map.Entry<Integer, Integer> pivotIndexElement = entries[right]; //we are choosing right index to be pivot index, but we can select any random one
int storeIndex = left;
for(int i=left;i<right; i++) {
if(entries[i].getValue() < pivotIndexElement.getValue()) {
// swap these entries
swap(entries, i, storeIndex);
storeIndex++;
}
}
swap(entries, right, storeIndex); // move pivot to its final place, meaning that,
// left side elements are now smaller than pivot and right elements
// are greater than pivot
return storeIndex;
}
// utility to swap i, j indexed elements
static void swap(Map.Entry<Integer, Integer>[] entries, int i, int j) {
Map.Entry<Integer, Integer> temp = entries[i];
entries[i] = entries[j];
entries[j] = temp;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n) time in average case, but takes O(n2) in worst case where nnumsheapify()
Space complexity: O(n) for storing elements count into HashMap
Solution 4 - using a technique used in BucketSort (easy and most efficient solution)
In this approach we will be using a technique from
Similar to all above approaches, we will first go through array and count how many times an element appeared and store this information into a HashMap
Next, we will create an array with the same size as input array, each element in the array will be a bucket (we will use an ArrayListHashMap
For example, say nums = [1, 1, 1, 3, 2, 2, 4, 1]k =2
Now, when we place these into bucketsbuckets = [null, [3, 4], [2], null, [1], null, null, null, null]
Now, we can loop this bucketsrightleftk
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
public class TopKFrequentElements {
/**
*
* Using a temporary array to store elements into respective indexes based the number of times they have appeared.
*/
static int[] usingBucketSortingTechnique(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
IntStream.of(nums).forEach(num->map.merge(num, 1, Integer::sum));
// Now, we will create n buckets to store count of number of times an element appeared
// index of array is the number of times an element appeared, within that bucket we will store elements
// that have appeared same number of times
List<Integer>[] buckets = new List[nums.length+1];// added +1 to keep n times appeared element into n(th) index
map.forEach((key, value) -> {
if (buckets[value] == null) {
buckets[value] = new ArrayList<>();
}
buckets[value].add(key);
});
int[] result = new int[k];
for(int i= buckets.length-1; i>=0 && k>0 ; i--) {
if(buckets[i] != null) {
List<Integer> elements = buckets[i];
for(int j=0; j<elements.size() && k>0; j++) {
result[k-1] = elements.get(j);
k--;
}
}
}
return result;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n) time where nnums
Space complexity: O(n) for storing elements count into HashMap
Above implementations source code can be found at