1086. High Five
Example 1:
Input: items =
[[1,91],[1,92],[2,93],[2,97],[1,60],[2,77],[1,65],[1,87],[1,100],[2,100],[2,76]]
Output: [[1,87],[2,88]] Explanation: The student with ID = 1 got
scores 91, 92, 60, 65, 87, and 100. Their top five average is (100 +
92 + 91 + 87 + 65) / 5 = 87. The student with ID = 2 got scores 93,
97, 77, 100, and 76. Their top five average is (100 + 97 + 93 + 77 +
76) / 5 = 88.6, but with integer division their average converts to
88.
Approach 1: Using Sorting
Intuition
Since we have to take into account the top 5 values corresponding to each id, we can sort the items array, first based on the id and next based on the score, i.e. items will be sorted based on increasing order of their ids. In case we have a tie for the value of ids, the items are then sorted based on decreasing order of the scores.
Algorithm
We can implement the idea for Approach 1 simply by having a custom comparator, which sorts the array based on the increasing order of ids and in case of tie, with the decreasing order of scores.
Below is the implementation of this approach.
[Java]
class Solution {
private int K;
public int[][] highFive(int[][] items) {
this.K = 5;
Arrays.sort(
items,
new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
if (a[0] != b[0])
// item with lower id goes first
return a[0] - b[0];
// in case of tie for ids, item with higher score goes first
return b[1] - a[1];
}
});
List<int[]> solution = new ArrayList<>();
int n = items.length;
int i = 0;
while (i < n) {
int id = items[i][0];
int sum = 0;
// obtain total using the top 5 scores
for (int k = i; k < i + this.K; ++k)
sum += items[k][1];
// ignore all the other scores for the same id
while (i < n && items[i][0] == id)
i++;
solution.add(new int[] {id, sum / this.K});
}
int[][] solutionArray = new int[solution.size()][];
return solution.toArray(solutionArray);
}
}
Approach 2: Using Map and Max Heap
Intuition
We can notice here that the only scores that contribute towards the calculation of the average are the top 5 scores. Therefore, if we only need the top 5 scores for each of the id we can ignore the rest of the scores. This can be done using a max heap.
Algorithm
We can maintain a max heap of all the scores for every id. This way, we can simply obtain the top 5 scores for each of the id by obtaining the first 5 elements from the max heap (which are indeed the top 5). Additionally, we would also need a map where the id of the student will be used as the key (as we want scores for the same id to be clubbed together in the same max heap).
Note that we’ll be using an ordered map (or a tree map) in this case as we want the final output scores to be in sorted order (which we can obtain by directly iterating over the keys of the map).
Below is the implementation of this approach.
[Java]
class Solution {
private int K;
public int[][] highFive(int[][] items) {
this.K = 5;
TreeMap<Integer, Queue<Integer>> allScores = new TreeMap<>();
for (int[] item : items) {
int id = item[0];
int score = item[1];
if (!allScores.containsKey(id))
// max heap
allScores.put(id, new PriorityQueue<>((a,b) -> b - a));
// Add score to the max heap
allScores.get(id).add(score);
}
List<int[]> solution = new ArrayList<>();
for (int id : allScores.keySet()) {
int sum = 0;
// obtain the top k scores (k = 5)
for (int i = 0; i < this.K; ++i)
sum += allScores.get(id).poll();
solution.add(new int[] {id, sum / this.K});
}
int[][] solutionArray = new int[solution.size()][];
return solution.toArray(solutionArray);
}
}
Approach 3: Using Map and Min Heap
Intuition
Till now, we have used a max heap using which we can obtain the top 5 elements by simply popping them out of the max heap one by one. However, for this, we have to store all the scores for each id in the max heap. This is inefficient. Since we are only interested in obtaining the top 5 scores, we do not need to store all the scores.
This can be made efficient using a min heap. In this approach, we will keep a min heap of the top 5 scores for each of the id. Whenever we encounter a score for an id, which is greater than the minimum score present in the min heap of that particular id, we update our min heap by adding the current score and removing the minimum score. This way, the size of the min heap never exceeds 5 which is a much more efficient implementation than keeping all the scores in the heap.
Algorithm
For each of the id, we can keep inserting all the scores in the corresponding min heap. Whenever we reach a state where the size of the min heap exceeds 5, we’ll simply remove the minimum element from the min heap, thereby making sure that the size of the min heap never exceeds 5.
Below is the implementation of this approach.
[Java]
class Solution {
private int K;
public int[][] highFive(int[][] items) {
this.K = 5;
TreeMap<Integer, Queue<Integer>> allScores = new TreeMap<>();
for (int[] item : items) {
int id = item[0];
int score = item[1];
if (!allScores.containsKey(id))
allScores.put(id, new PriorityQueue<>());
// insert the score in the min heap
allScores.get(id).add(score);
// remove the minimum element from the min heap in case the size of the min heap exceeds 5
if (allScores.get(id).size() > this.K)
allScores.get(id).poll();
}
List<int[]> solution = new ArrayList<>();
for (int id : allScores.keySet()) {
int sum = 0;
// min heap contains the top 5 scores
for (int i = 0; i < this.K; ++i)
sum += allScores.get(id).poll();
solution.add(new int[] {id, sum / this.K});
}
int[][] solutionArray = new int[solution.size()][];
return solution.toArray(solutionArray);
}
}
Approach 4: Using priority queue for each id
[Python]
class Solution:
def highFive(self, items: List[List[int]]) -> List[List[int]]:
d = collections.defaultdict(list)
for idx, val in items:
heapq.heappush(d[idx], val)
if len(d[idx]) > 5:
heapq.heappop(d[idx])
res = [[i, sum(d[i]) // len(d[i])] for i in sorted(d)]
return res
Approach 5: Running Length Encoding
first sort reversely by id and score, and then calculate average for each id
[Python]
class Solution:
def highFive(self, items: List[List[int]]) -> List[List[int]]:
items.sort(reverse=True)
res = []
curr = []
idx = items[0][0]
for i, val in items:
if i == idx:
if len(curr) < 5:
curr.append(val)
else:
res.append([idx, sum(curr) // len(curr)])
curr = [val]
idx = i
res.append([idx, sum(curr) // len(curr)])
res = res[::-1]
return res
Approach 6: Using priority queue for each id
[Python]
class Solution(object):
def highFive(self, items):
res = {}
for i in items:
if i[0] in res:
res[i[0]].append(i[1])
res[i[0]] = sorted(res[i[0]], reverse=True)
else:
res[i[0]] = [i[1]]
return [[i,sum(res[i][:5])/5] for i in res.keys()]