LeetCode 1792: Maximum Average Pass Ratio (Greedy / Priority Queue)
LeetCode 1792Source: https://leetcode.com/problems/maximum-average-pass-ratio/
English
Each extra student changes a class ratio from p/t to (p+1)/(t+1). The key is to maximize marginal gain:gain = (p+1)/(t+1) - p/t.
Always assign the next student to the class with the largest current gain, then recompute that class gain and push it back. This is a classic greedy with max-heap.
class Solution {
static class Node {
int p, t;
Node(int p, int t) { this.p = p; this.t = t; }
double gain() { return (double)(p + 1) / (t + 1) - (double)p / t; }
double ratio() { return (double)p / t; }
}
public double maxAverageRatio(int[][] classes, int extraStudents) {
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> Double.compare(b.gain(), a.gain()));
for (int[] c : classes) pq.offer(new Node(c[0], c[1]));
while (extraStudents-- > 0) {
Node cur = pq.poll();
cur.p++;
cur.t++;
pq.offer(cur);
}
double sum = 0.0;
while (!pq.isEmpty()) sum += pq.poll().ratio();
return sum / classes.length;
}
}type Node struct{ p, t int }
type MaxHeap []Node
func gain(n Node) float64 {
return float64(n.p+1)/float64(n.t+1) - float64(n.p)/float64(n.t)
}
func (h MaxHeap) Len() int { return len(h) }
func (h MaxHeap) Less(i, j int) bool { return gain(h[i]) > gain(h[j]) }
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MaxHeap) Push(x interface{}) { *h = append(*h, x.(Node)) }
func (h *MaxHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func maxAverageRatio(classes [][]int, extraStudents int) float64 {
h := &MaxHeap{}
heap.Init(h)
for _, c := range classes {
heap.Push(h, Node{c[0], c[1]})
}
for extraStudents > 0 {
cur := heap.Pop(h).(Node)
cur.p++
cur.t++
heap.Push(h, cur)
extraStudents--
}
sum := 0.0
for h.Len() > 0 {
cur := heap.Pop(h).(Node)
sum += float64(cur.p) / float64(cur.t)
}
return sum / float64(len(classes))
}class Solution {
public:
struct Node { int p, t; };
static double gain(const Node& n) {
return (double)(n.p + 1) / (n.t + 1) - (double)n.p / n.t;
}
double maxAverageRatio(vector<vector<int>>& classes, int extraStudents) {
auto cmp = [](const Node& a, const Node& b) { return gain(a) < gain(b); };
priority_queue<Node, vector<Node>, decltype(cmp)> pq(cmp);
for (auto& c : classes) pq.push({c[0], c[1]});
while (extraStudents--) {
Node cur = pq.top(); pq.pop();
cur.p++; cur.t++;
pq.push(cur);
}
double sum = 0.0;
while (!pq.empty()) {
Node cur = pq.top(); pq.pop();
sum += (double)cur.p / cur.t;
}
return sum / classes.size();
}
};import heapq
from typing import List
class Solution:
def maxAverageRatio(self, classes: List[List[int]], extraStudents: int) -> float:
def gain(p: int, t: int) -> float:
return (p + 1) / (t + 1) - p / t
heap = [(-gain(p, t), p, t) for p, t in classes]
heapq.heapify(heap)
for _ in range(extraStudents):
_, p, t = heapq.heappop(heap)
p += 1
t += 1
heapq.heappush(heap, (-gain(p, t), p, t))
return sum(p / t for _, p, t in heap) / len(classes)class MaxHeap {
constructor(cmp) { this.a = []; this.cmp = cmp; }
push(x) { this.a.push(x); this.up(this.a.length - 1); }
pop() {
const top = this.a[0], last = this.a.pop();
if (this.a.length) { this.a[0] = last; this.down(0); }
return top;
}
up(i) {
while (i > 0) {
const p = (i - 1) >> 1;
if (!this.cmp(this.a[i], this.a[p])) break;
[this.a[i], this.a[p]] = [this.a[p], this.a[i]];
i = p;
}
}
down(i) {
const n = this.a.length;
while (true) {
let b = i, l = i * 2 + 1, r = l + 1;
if (l < n && this.cmp(this.a[l], this.a[b])) b = l;
if (r < n && this.cmp(this.a[r], this.a[b])) b = r;
if (b === i) break;
[this.a[i], this.a[b]] = [this.a[b], this.a[i]];
i = b;
}
}
}
var maxAverageRatio = function(classes, extraStudents) {
const gain = (p, t) => (p + 1) / (t + 1) - p / t;
const hp = new MaxHeap((x, y) => x.g > y.g);
for (const [p, t] of classes) hp.push({ p, t, g: gain(p, t) });
while (extraStudents-- > 0) {
const cur = hp.pop();
cur.p++; cur.t++; cur.g = gain(cur.p, cur.t);
hp.push(cur);
}
let sum = 0;
while (hp.a.length) {
const cur = hp.pop();
sum += cur.p / cur.t;
}
return sum / classes.length;
};中文
每次加入一个“必过学生”,某个班级通过率从 p/t 变成 (p+1)/(t+1)。核心是比较“增益”:gain = (p+1)/(t+1) - p/t。
贪心策略是每一步都把学生分配给当前增益最大的班级,然后更新该班级并重新入堆。用大顶堆可以高效实现。
class Solution {
static class Node {
int p, t;
Node(int p, int t) { this.p = p; this.t = t; }
double gain() { return (double)(p + 1) / (t + 1) - (double)p / t; }
double ratio() { return (double)p / t; }
}
public double maxAverageRatio(int[][] classes, int extraStudents) {
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> Double.compare(b.gain(), a.gain()));
for (int[] c : classes) pq.offer(new Node(c[0], c[1]));
while (extraStudents-- > 0) {
Node cur = pq.poll();
cur.p++;
cur.t++;
pq.offer(cur);
}
double sum = 0.0;
while (!pq.isEmpty()) sum += pq.poll().ratio();
return sum / classes.length;
}
}type Node struct{ p, t int }
type MaxHeap []Node
func gain(n Node) float64 {
return float64(n.p+1)/float64(n.t+1) - float64(n.p)/float64(n.t)
}
func (h MaxHeap) Len() int { return len(h) }
func (h MaxHeap) Less(i, j int) bool { return gain(h[i]) > gain(h[j]) }
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MaxHeap) Push(x interface{}) { *h = append(*h, x.(Node)) }
func (h *MaxHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func maxAverageRatio(classes [][]int, extraStudents int) float64 {
h := &MaxHeap{}
heap.Init(h)
for _, c := range classes {
heap.Push(h, Node{c[0], c[1]})
}
for extraStudents > 0 {
cur := heap.Pop(h).(Node)
cur.p++
cur.t++
heap.Push(h, cur)
extraStudents--
}
sum := 0.0
for h.Len() > 0 {
cur := heap.Pop(h).(Node)
sum += float64(cur.p) / float64(cur.t)
}
return sum / float64(len(classes))
}class Solution {
public:
struct Node { int p, t; };
static double gain(const Node& n) {
return (double)(n.p + 1) / (n.t + 1) - (double)n.p / n.t;
}
double maxAverageRatio(vector<vector<int>>& classes, int extraStudents) {
auto cmp = [](const Node& a, const Node& b) { return gain(a) < gain(b); };
priority_queue<Node, vector<Node>, decltype(cmp)> pq(cmp);
for (auto& c : classes) pq.push({c[0], c[1]});
while (extraStudents--) {
Node cur = pq.top(); pq.pop();
cur.p++; cur.t++;
pq.push(cur);
}
double sum = 0.0;
while (!pq.empty()) {
Node cur = pq.top(); pq.pop();
sum += (double)cur.p / cur.t;
}
return sum / classes.size();
}
};import heapq
from typing import List
class Solution:
def maxAverageRatio(self, classes: List[List[int]], extraStudents: int) -> float:
def gain(p: int, t: int) -> float:
return (p + 1) / (t + 1) - p / t
heap = [(-gain(p, t), p, t) for p, t in classes]
heapq.heapify(heap)
for _ in range(extraStudents):
_, p, t = heapq.heappop(heap)
p += 1
t += 1
heapq.heappush(heap, (-gain(p, t), p, t))
return sum(p / t for _, p, t in heap) / len(classes)class MaxHeap {
constructor(cmp) { this.a = []; this.cmp = cmp; }
push(x) { this.a.push(x); this.up(this.a.length - 1); }
pop() {
const top = this.a[0], last = this.a.pop();
if (this.a.length) { this.a[0] = last; this.down(0); }
return top;
}
up(i) {
while (i > 0) {
const p = (i - 1) >> 1;
if (!this.cmp(this.a[i], this.a[p])) break;
[this.a[i], this.a[p]] = [this.a[p], this.a[i]];
i = p;
}
}
down(i) {
const n = this.a.length;
while (true) {
let b = i, l = i * 2 + 1, r = l + 1;
if (l < n && this.cmp(this.a[l], this.a[b])) b = l;
if (r < n && this.cmp(this.a[r], this.a[b])) b = r;
if (b === i) break;
[this.a[i], this.a[b]] = [this.a[b], this.a[i]];
i = b;
}
}
}
var maxAverageRatio = function(classes, extraStudents) {
const gain = (p, t) => (p + 1) / (t + 1) - p / t;
const hp = new MaxHeap((x, y) => x.g > y.g);
for (const [p, t] of classes) hp.push({ p, t, g: gain(p, t) });
while (extraStudents-- > 0) {
const cur = hp.pop();
cur.p++; cur.t++; cur.g = gain(cur.p, cur.t);
hp.push(cur);
}
let sum = 0;
while (hp.a.length) {
const cur = hp.pop();
sum += cur.p / cur.t;
}
return sum / classes.length;
};
Comments