当前位置: 首页 > news >正文

【LeetCode 每日一题】1792. 最大平均通过率——贪心 + 优先队列

Problem: 1792. 最大平均通过率

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N log N + K log N)
    • 空间复杂度:O(N)

整体思路

这段代码旨在解决一个优化问题:给定一组班级,每个班级有 pass 个通过人数和 total 个总人数,以及 extraStudents 个额外的学生。你需要将这些额外的学生分配给各个班级,使得所有班级的平均通过率最大化。

该算法采用了一种 贪心策略 (Greedy Strategy),并利用 优先队列 (Priority Queue) 这一数据结构来高效地实现。其核心思想是:每一次分配,都把一个额外的学生分配给那个能带来最大通过率增益的班级。

  1. 定义“增益”

    • 算法的关键在于如何量化“增益”。如果一个班级当前有 p 个通过人数和 t 个总人数,其通过率为 p / t
    • 如果我们给这个班级增加一个学生(假设这个学生也会通过,这是题目隐含的条件,为了最大化通过率),那么新的通过人数为 p+1,总人数为 t+1,新的通过率为 (p+1) / (t+1)
    • 因此,增加一个学生带来的通过率增益(p+1)/(t+1) - p/t
    • 我们的贪心策略就是,在每一步都选择使这个增益值最大的班级进行分配。
  2. 数据结构选择:优先队列

    • 为了在每一步都能快速找到增益最大的班级,优先队列是理想的数据结构。
    • 我们将所有班级放入一个**最大堆(Max-Heap)**中,堆的排序标准就是每个班级增加一个学生后所能带来的通过率增益。
  3. 排序标准的数学推导

    • 直接比较浮点数 (p+1)/(t+1) - p/t 可能会有精度问题,而且效率不高。
    • 比较两个班级 ab 的增益,即比较 (a_p+1)/(a_t+1) - a_p/a_t(b_p+1)/(b_t+1) - b_p/b_t 的大小。
    • 通过通分和化简,增益 (p+1)/(t+1) - p/t 可以变为 (t - p) / (t * (t+1))
    • 所以,比较增益就等价于比较 (a_t - a_p) / (a_t * (a_t+1))(b_t - b_p) / (b_t * (b_t+1))
    • 为了避免浮点数除法,我们可以进行交叉相乘:比较 (a_t - a_p) * b_t * (b_t+1)(b_t - b_p) * a_t * (a_t+1) 的大小。
    • 这就是代码中 PriorityQueueComparator 里的 xy 的由来(其中 a[0]passa[1]total)。
    • Long.compare(y, x) 实现了最大堆的效果:如果 y 大于 x,则 b 的增益大于 ab 的优先级更高。
  4. 算法执行流程

    • 初始化:创建一个根据上述增益公式排序的最大优先队列,并将所有初始班级信息加入队列。
    • 贪心分配:循环 extraStudents 次。在每次循环中:
      a. 从优先队列中取出 poll() 增益最大的班级。
      b. 将该班级的通过人数和总人数都加 1。
      c. 将更新后的班级信息重新放回 offer() 优先队列。由于其 passtotal 已经改变,它下次能带来的增益也会相应改变,队列会自动调整其位置。
    • 计算最终结果:当所有额外的学生都分配完毕后,队列中存储的就是最优分配方案下所有班级的最终状态。
    • 遍历队列,累加所有班级的最终通过率,然后除以班级总数,得到平均通过率。

完整代码

import java.util.PriorityQueue;class Solution {/*** 将 extraStudents 分配给各个班级,以最大化所有班级的平均通过率。* @param classes 一个二维数组,每个子数组 [pass, total] 代表一个班级的通过人数和总人数。* @param extraStudents 额外的学生数量。* @return 最大的平均通过率。*/public double maxAverageRatio(int[][] classes, int extraStudents) {// 创建一个最大优先队列。排序标准是:增加一个学生后,哪个班级的通过率增益最大。PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {// a, b 分别是两个班级 [pass, total]// 增益公式 E(p,t) = (p+1)/(t+1) - p/t = (t-p) / (t*(t+1))// 比较 E(a) 和 E(b) 就是比较 (a_t-a_p)/(a_t*(a_t+1)) 和 (b_t-b_p)/(b_t*(b_t+1))// 为避免浮点数运算,交叉相乘比较:// (a_t-a_p) * b_t * (b_t+1)  vs  (b_t-b_p) * a_t * (a_t+1)// 使用 long 类型防止整数溢出long x = 1L * (a[1] - a[0]) * b[1] * (b[1] + 1);long y = 1L * (b[1] - b[0]) * a[1] * (a[1] + 1);// Long.compare(y, x) 实现最大堆:如果 y > x,则 b 的增益大,b 的优先级高。return Long.compare(y, x);});int n = classes.length;// 将所有初始班级加入优先队列for (int[] clazz : classes) {pq.add(clazz);}// 步骤 2: 贪心分配 extraStudentswhile (extraStudents-- > 0) {// 取出当前增益最大的班级int[] temp = pq.poll();// 分配一个学生给这个班级temp[0]++;temp[1]++;// 将更新后的班级放回队列,队列会自动重新排序pq.offer(temp);}// 步骤 3: 计算最终的平均通过率double ans = 0;while (!pq.isEmpty()) {int[] temp = pq.poll();// 累加每个班级的最终通过率ans += 1.0 * temp[0] / temp[1];}// 返回平均值return ans / n;}
}

时空复杂度

时间复杂度:O(N log N + K log N)

  1. 优先队列初始化
    • N 个班级加入优先队列。每次 add 操作的时间复杂度是 O(log N)。
    • 因此,初始化总时间为 O(N log N)
  2. 贪心分配循环
    • while 循环执行 K 次,其中 KextraStudents 的数量。
    • 在每次循环中,执行一次 poll() 和一次 offer() 操作。这两个操作的时间复杂度都是 O(log N),因为队列的大小始终是 N
    • 因此,这部分的总时间为 O(K log N)
  3. 结果计算
    • 最后的 while 循环从队列中取出所有 N 个元素,每次 poll() 操作是 O(log N),总时间为 O(N log N)。

综合分析
总的时间复杂度是 O(N log N) + O(K log N) + O(N log N) = O((N+K) log N)

空间复杂度:O(N)

  1. 主要存储开销:算法使用了一个优先队列 pq 来存储所有的班级信息。
  2. 空间大小
    • 优先队列 pq 的大小始终是 N,即班级的总数。
    • 每个元素是一个 int[2] 数组,占用的空间是常数。
  3. 综合分析
    • 算法所需的额外空间主要由优先队列决定,其大小与班级的数量 N 成线性关系。
    • 因此,空间复杂度为 O(N)

文章转载自:

http://9NzseA5h.tnwwL.cn
http://h6Un9S9w.tnwwL.cn
http://fKod4Exo.tnwwL.cn
http://nBzwrDIq.tnwwL.cn
http://xfzPDCPd.tnwwL.cn
http://8JDx0Mgc.tnwwL.cn
http://mFmah3Bl.tnwwL.cn
http://N9XmL4Ql.tnwwL.cn
http://xIBB7YnF.tnwwL.cn
http://Ymo4DOcs.tnwwL.cn
http://x4NJcipz.tnwwL.cn
http://MrpLtopi.tnwwL.cn
http://onqoQBDz.tnwwL.cn
http://9677QmoI.tnwwL.cn
http://1RFPFXOL.tnwwL.cn
http://dK6Afd5B.tnwwL.cn
http://7o3coj5l.tnwwL.cn
http://0iqLw1YY.tnwwL.cn
http://WC7cAVR4.tnwwL.cn
http://gDu8NSZF.tnwwL.cn
http://HjDWxiUf.tnwwL.cn
http://aDA51pfE.tnwwL.cn
http://sLkjbRa8.tnwwL.cn
http://PQKOYrOc.tnwwL.cn
http://JFtYEpZJ.tnwwL.cn
http://XnQ8gXcB.tnwwL.cn
http://7HSPPGfk.tnwwL.cn
http://RFiriTDw.tnwwL.cn
http://YTo0vcYQ.tnwwL.cn
http://ngKb65Uo.tnwwL.cn
http://www.dtcms.com/a/384351.html

相关文章:

  • 【深度学习计算机视觉】05:多尺度目标检测
  • Docker将镜像搬移到其他服务上的方法
  • WiseAI-百度研发的AI智能聊天产品
  • .NET驾驭Word之力:理解Word对象模型核心 (Application, Document, Range)
  • 【JAVA接口自动化】JAVA如何读取Yaml文件
  • Redis全面指南:从入门到精通
  • Word在WPS和Office中给图片添加黑色边框
  • C++ Lua组合拳:构建高性能系统配置管理框架
  • 数据库编程--完成简单的信息登录系统+思维导图
  • Spring Boot 深入剖析:SpringApplicationRunListener
  • 【新手指南】解析Laf.run上的GET API接口
  • 如何批量删除 iPhone/iPad 上的照片 [7 种方法
  • Spring Boot 日志体系全面解析:从 SLF4J 到 Logback、Log4j2 与 Lombok 超详细!!
  • springboot创建请求处理
  • 08-Redis 字符串类型全解析:从命令实操到业务场景落地
  • 学习海康VisionMaster之字符缺陷检测
  • CAD画图:002软件界面操作
  • 解锁全球业务潜能:AWS全球网络加速解决方案深度解析
  • HTTPS Everywhere 是什么?HTTPS 插件作用、iOS 抓包失败原因解析与常见抓包工具对比
  • 【C++】STL详解(七)—stack和queue的介绍及使用
  • 20250912在荣品RD-RK3588-MID开发板的Android13系统下拿掉卡迪屏的reset引脚的下拉复位波形
  • 在线图书借阅平台的设计与实现 —— 基于飞算JavaAI的实战开发全流程与优化实践
  • Git : 分支管理和远程仓库
  • 当传统金融遇上AI智能:AIStock系统深度技术解析
  • 大数据如何捕捉你的爱好?如何实现跨站用户行为分析?
  • 用OpenCV CSRT实现实时目标跟踪
  • 13.Linux OpenSSH 服务管理
  • 微算法科技(NASDAQ: MLGO)研发基于量子密钥图像的量子图像加密算法,提供更高安全性的图像保护方案
  • LAMP 环境部署
  • Java程序设计:Eclipse 安装和使用