一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试。给你一个二维数组 classes
,其中 classes[i] = [passi, totali]
,表示你提前知道了第 i
个班级总共有 totali
个学生,其中只有 passi
个学生可以通过考试。
给你一个整数 extraStudents
,表示额外有 extraStudents
个聪明的学生,他们 一定 能通过任何班级的期末考。你需要给这 extraStudents
个学生每人都安排一个班级,使得 所有 班级的 平均 通过率 最大 。
一个班级的 通过率 等于这个班级通过考试的学生人数除以这个班级的总人数。 平均通过率 是所有班级的通过率之和除以班级数目。
请你返回在安排这 extraStudents
个学生去对应班级后的 最大 平均通过率。与标准答案误差范围在 10-5
以内的结果都会视为正确结果。
示例 1:
**输入:** classes = [[1,2],[3,5],[2,2]], extraStudents = 2
**输出:** 0.78333
**解释:** 你可以将额外的两个学生都安排到第一个班级,平均通过率为 (3/4 + 3/5 + 2/2) / 3 = 0.78333 。
示例 2:
**输入:** classes = [[2,4],[3,9],[4,5],[2,10]], extraStudents = 4
**输出:** 0.53485
提示:
1 <= classes.length <= 105
classes[i].length == 2
1 <= passi <= totali <= 105
1 <= extraStudents <= 105
方法一:优先队列 思路与算法
由于班级总数不会变化,因此题目所求「最大化平均通过率」等价于「最大化总通过率」。设某个班级的人数为 total,其中通过考试的人数为 pass,那么给这个班级安排一个额外通过考试的学生,其通过率会增加:
\textit{pass} + 1}{\textit{total} + 1} - \textit{pass} }{\textit{total}
我们会优先选择通过率增加量最大的班级,这样做的正确性在于给同一个班级不断地增加安排的学生数量时,其增加的通过率是单调递减的,即:
\textit{pass} + 2}{\textit{total} + 2} - \textit{pass} + 1}{\textit{total} + 1} < \textit{pass} + 1}{\textit{total} + 1} - \textit{pass} }{\textit{total}
因此当以下条件满足时,班级 j 比班级 i 优先级更大:
\textit{pass}_i + 1}{\textit{total}_i + 1} - \textit{pass}_i}{\textit{total}_i} < \textit{pass}_j + 1}{\textit{total}_j + 1} - \textit{pass}_j}{\textit{total}_j
化简后可得:
(\textit{total}_j + 1) \times (\textit{total}_j) \times (\textit{total}_i - \textit{pass}_i) < (\textit{total}_i + 1) \times (\textit{total}_i) \times (\textit{total}_j - \textit{pass}_j)
我们按照上述比较规则将每个班级放入优先队列中,进行 extraStudents 次操作。每一次操作,我们取出优先队列的堆顶元素,令其 pass 和 total 分别加 1,然后再放回优先队列。
最后我们遍历优先队列的每一个班级,计算其平均通过率即可得到答案。
代码
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Entry : __slots__ = 'p' , 't' def __init__ (self, p: int , t: int ): self.p = p self.t = t def __lt__ (self, b: 'Entry' ) -> bool : return (self.t - self.p) * b.t * (b.t + 1 ) > (b.t - b.p) * self.t * (self.t + 1 ) class Solution : def maxAverageRatio (self, classes: List [List [int ]], extraStudents: int ) -> float : h = [Entry(*c) for c in classes] heapify(h) for _ in range (extraStudents): heapreplace(h, Entry(h[0 ].p + 1 , h[0 ].t + 1 )) return sum (e.p / e.t for e in h) / len (h)
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution {public : struct Ratio { int pass, total; bool operator < (const Ratio& oth) const { return (long long ) (oth.total + 1 ) * oth.total * (total - pass) < (long long ) (total + 1 ) * total * (oth.total - oth.pass); } }; double maxAverageRatio (vector<vector<int >>& classes, int extraStudents) { priority_queue<Ratio> q; for (auto &c : classes) { q.push ({c[0 ], c[1 ]}); } for (int i = 0 ; i < extraStudents; i++) { auto [pass, total] = q.top (); q.pop (); q.push ({pass + 1 , total + 1 }); } double res = 0 ; for (int i = 0 ; i < classes.size (); i++) { auto [pass, total] = q.top (); q.pop (); res += 1.0 * pass / total; } return res / classes.size (); } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public double maxAverageRatio (int [][] classes, int extraStudents) { PriorityQueue<int []> pq = new PriorityQueue <int []>((a, b) -> { long val1 = (long ) (b[1 ] + 1 ) * b[1 ] * (a[1 ] - a[0 ]); long val2 = (long ) (a[1 ] + 1 ) * a[1 ] * (b[1 ] - b[0 ]); if (val1 == val2) { return 0 ; } return val1 < val2 ? 1 : -1 ; }); for (int [] c : classes) { pq.offer(new int []{c[0 ], c[1 ]}); } for (int i = 0 ; i < extraStudents; i++) { int [] arr = pq.poll(); int pass = arr[0 ], total = arr[1 ]; pq.offer(new int []{pass + 1 , total + 1 }); } double res = 0 ; for (int i = 0 ; i < classes.length; i++) { int [] arr = pq.poll(); int pass = arr[0 ], total = arr[1 ]; res += 1.0 * pass / total; } return res / classes.length; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func maxAverageRatio (classes [][]int , extraStudents int ) (ans float64 ) { h := hp(classes) heap.Init(&h) for ; extraStudents > 0 ; extraStudents-- { h[0 ][0 ]++ h[0 ][1 ]++ heap.Fix(&h, 0 ) } for _, c := range h { ans += float64 (c[0 ]) / float64 (c[1 ]) } return ans / float64 (len (classes)) } type hp [][]int func (h hp) Len() int { return len (h) }func (h hp) Less(i, j int ) bool { a, b := h[i], h[j]; return (a[1 ]-a[0 ])*b[1 ]*(b[1 ]+1 ) > (b[1 ]-b[0 ])*a[1 ]*(a[1 ]+1 ) }func (h hp) Swap(i, j int ) { h[i], h[j] = h[j], h[i] }func (hp) Push(interface {}) {}func (hp) Pop() (_ interface {}) { return }
复杂度分析
时间复杂度:O((n + m)\log n) 或 O(n + m\log n),其中 n 为 classes 的长度,m 等于 extraStudents。每次从优先队列中取出或者放入元素的时间复杂度为 O(\log n),共需操作 O(n + m) 次,故总复杂度为 O((n + m)\log n)。堆化写法的时间复杂度为 O(n + m\log n)。
空间复杂度:O(n) 或 O(1)。使用优先队列需要用到 O(n) 的空间,但若直接在 classes 上原地堆化,则可以做到 O(1) 额外空间。