目录
最大化班级平均通过率 —— 贪心 + 堆优化方案详解
题目描述
学校有若干班级,每个班级有若干学生,并且每个班级已有一定数量的学生通过期末考试。现在给你一个二维数组 classes,其中 表示第
i 个班级中共有 个学生,其中只有
个学生能通过考试。
此外,你还有 extraStudents 个聪明学生,这些学生 一定能通过任何班级的考试。你的任务是把这 extraStudents 个学生分配到各个班级中,使得所有班级的平均通过率最大。
- 通过率:班级中通过考试学生数除以该班级总人数
- 平均通过率:所有班级的通过率之和除以班级数目
目标: 返回分配完所有额外学生后的最大平均通过率。结果允许与标准答案误差在 10^-5 以内。
输入输出示例
示例 1:
输入:
classes = [[1,2],[3,5],[2,2]]
extraStudents = 2
输出:
0.78333
解释:
将两个额外的学生都分配到第一个班级,班级状态变为:
第一个班级:pass=3, total=4, 通过率=3/4=0.75
第二个班级:通过率=3/5=0.6
第三个班级:通过率=2/2=1.0
平均通过率 = (0.75 + 0.6 + 1.0) / 3 = 0.78333
示例 2:
输入:
classes = [[2,4],[3,9],[4,5],[2,10]]
extraStudents = 4
输出:
0.53485
解题分析
核心难点
- 额外学生可以自由分配,但要让平均通过率最大化。
- 如何判断“给哪个班级分配额外学生,能带来最大增益”?
通过率增益计算
一个班级的当前通过率是:
给班级分配一个额外的聪明学生后,通过率变为:
增益(gain)是:
显然,我们每次都应该选择增益最大的班级,优先把额外学生分配给它。
贪心策略
- 计算每个班级增加一个学生的增益。
- 选择增益最大的班级分配一个额外学生。
- 更新该班级的
pass和total。 - 重新计算该班级的新增益。
- 重复上述过程,直到分配完所有额外学生。
数据结构选择
- 使用最大堆(优先队列)存储班级的增益和当前状态。
- Python 自带的
heapq是最小堆,因此存储负的增益值来模拟最大堆。
解题代码
import heapq
from typing import List
class Solution:
def maxAverageRatio(self, classes: List[List[int]], extraStudents: int) -> float:
def gain(pass_, total):
# 计算分配一个额外学生后带来的增益
return (pass_ + 1) / (total + 1) - pass_ / total
# 建立最大堆,存储(-增益, pass, total)
heap = []
for pass_, total in classes:
g = gain(pass_, total)
heapq.heappush(heap, (-g, pass_, total))
# 分配额外学生
for _ in range(extraStudents):
g, pass_, total = heapq.heappop(heap)
pass_ += 1
total += 1
new_g = gain(pass_, total)
heapq.heappush(heap, (-new_g, pass_, total))
# 计算最终平均通过率
total_classes = len(classes)
result = 0
while heap:
_, pass_, total = heapq.heappop(heap)
result += pass_ / total
return result / total_classes
复杂度分析
- 假设班级数为
n,额外学生数为m。 - 每次分配学生,我们执行一次堆操作(弹出+插入),时间复杂度为
O(log n)。 - 总共分配
m个学生,时间复杂度为O(m log n)。 - 空间复杂度为
O(n),存储堆。
思路对比和优化讨论
- 暴力尝试每个分配方案明显不可行,组合爆炸,效率极低。
- 贪心策略是基于“每次选最大增益”局部最优选择,整体最优得到保证。
- 使用堆保证每次获取最大增益的班级是高效的。
- 对于非常大规模数据,也可以考虑用数学方法或并行化,但当前方案已是最佳实用解。
结论
通过贪心和堆的巧妙结合,我们可以高效地将额外的聪明学生分配给那些增益最大的班级,从而最大化所有班级的平均通过率。

被折叠的 条评论
为什么被折叠?



