{x}
blog image

Maximum Average Pass Ratio

There is a school that has classes of students and each class will be having a final exam. You are given a 2D integer array classes, where classes[i] = [passi, totali]. You know beforehand that in the ith class, there are totali total students, but only passi number of students will pass the exam.

You are also given an integer extraStudents. There are another extraStudents brilliant students that are guaranteed to pass the exam of any class they are assigned to. You want to assign each of the extraStudents students to a class in a way that maximizes the average pass ratio across all the classes.

The pass ratio of a class is equal to the number of students of the class that will pass the exam divided by the total number of students of the class. The average pass ratio is the sum of pass ratios of all the classes divided by the number of the classes.

Return the maximum possible average pass ratio after assigning the extraStudents students. Answers within 10-5 of the actual answer will be accepted.

 

Example 1:

Input: classes = [[1,2],[3,5],[2,2]], extraStudents = 2
Output: 0.78333
Explanation: You can assign the two extra students to the first class. The average pass ratio will be equal to (3/4 + 3/5 + 2/2) / 3 = 0.78333.

Example 2:

Input: classes = [[2,4],[3,9],[4,5],[2,10]], extraStudents = 4
Output: 0.53485

 

Constraints:

  • 1 <= classes.length <= 105
  • classes[i].length == 2
  • 1 <= passi <= totali <= 105
  • 1 <= extraStudents <= 105

Solution Explanation: Maximum Average Pass Ratio

This problem asks to maximize the average pass ratio of classes by strategically assigning extra students. The key insight is that assigning a student to a class with a smaller class size yields a larger increase in the pass ratio.

Approach: Greedy with Priority Queue

The optimal solution employs a greedy approach using a priority queue (max-heap). The strategy is to iteratively assign the extra students to the classes that offer the largest increase in pass ratio per student. We don't need to explore all possible combinations of student assignments because of the greedy nature of this algorithm.

  1. Calculate the potential improvement: For each class [pass_i, total_i], calculate the increase in the pass ratio if one extra student is added: (pass_i + 1) / (total_i + 1) - pass_i / total_i. This represents the potential improvement achieved by assigning a student to that specific class.

  2. Priority Queue: Use a max-heap (priority queue) to store the classes, prioritized by their potential improvement calculated in the previous step. The classes with higher potential improvement will have higher priority.

  3. Iterative Assignment: Repeatedly extract the class with the highest potential improvement from the heap (the class that will see the most significant boost in pass ratio). Assign an extra student to this class by incrementing both pass_i and total_i. Then, recalculate the new potential improvement for this updated class and reinsert it into the heap. Repeat this process for all extraStudents.

  4. Calculate Average Pass Ratio: After assigning all extra students, iterate through the updated classes and compute the average pass ratio.

Time and Space Complexity

  • Time Complexity: The dominant operations are heap operations (insertion and extraction). Each of the extraStudents iterations involves a heappop and heappush, which are O(log n) operations, where n is the number of classes. Therefore, the overall time complexity is O(extraStudents * log n). In the worst case, this could be O(n log n) if extraStudents is approximately equal to n.

  • Space Complexity: The priority queue stores at most n elements (the number of classes), so the space complexity is O(n).

Code Implementation (Python)

import heapq
 
def maxAverageRatio(classes, extraStudents):
    heap = []
    for pass_i, total_i in classes:
        improvement = (pass_i + 1) / (total_i + 1) - pass_i / total_i
        heapq.heappush(heap, (-improvement, pass_i, total_i)) # Use negative for max-heap
 
    for _ in range(extraStudents):
        improvement, pass_i, total_i = heapq.heappop(heap)
        pass_i += 1
        total_i += 1
        new_improvement = (pass_i + 1) / (total_i + 1) - pass_i / total_i
        heapq.heappush(heap, (-new_improvement, pass_i, total_i))
 
    total_ratio = 0
    for _, pass_i, total_i in heap:
        total_ratio += pass_i / total_i
 
    return total_ratio / len(classes)
 

The code in other languages (Java, C++, Go) follows the same algorithmic structure, only differing in the syntax and specific data structures used for the priority queue. The core idea of the greedy approach and the use of a max-heap remain consistent.