🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!
📌 特别说明: 文中部分技术问题来源于真实生产环境及网络公开案例,均经过精挑细选与系统化整理,并结合多位一线资深架构师和工程师多年实战经验沉淀,提炼出多种经过验证的高可行性解决方案,供开发者们参考与借鉴。
欢迎 关注、收藏并订阅本专栏,持续更新的干货内容将与您同行,让我们携手精进,技术跃迁,步步高升!

全文目录:
📢 问题描述
问题来源:https://ask.csdn.net/questions/xxx
描述:
童程学校原来只有一台饮水机,同学们只能排队打水,消耗了课间时间。学校决定改善设备,增加到了r台饮水机。但是鲁丽还是想知道,学生们按照一个顺序去打水,总的花费时间最少,这样不是更节省时间吗?
现在,有n 个同学排队到 r 台饮水机去打水,他们装满水杯的时间 t1、t2…tn,这些数为整数,应如何安排他们的打水顺序才能使他们总共花费的时间最少?
输入描述:
第一行 n,r (n≤300,r≤100)
第二行为 n 个同学打水所用的时间 ti ( ti≤30)
输出描述:
最少的花费时间
样例输入1
4 2
3 2 1 2
样例输出2
11
提示
数据范围与提示:
n≤300,r≤100,ti≤30

📣 请知悉:如下方案不保证一定适配你的问题!
如下是针对上述问题进行专业角度剖析答疑,不喜勿喷,仅供参考:

✅️问题理解
这是一个多台饮水机排队优化的贪心算法问题。具体分析如下:
问题核心:
- n个同学需要到r台饮水机打水
- 每个同学打水时间为ti
- 目标是最小化所有同学的总完成时间(不是总等待时间)
关键理解:
- 总花费时间 = 每个同学完成打水的时刻之和
- 饮水机可以并行工作,需要合理分配同学到不同饮水机
- 这是一个典型的任务调度优化问题
样例分析:
4个同学,2台饮水机,时间[3,2,1,2]
最优分配:
饮水机1: 时间1的同学(0→1) + 时间2的同学(1→3) = 完成时间[1,3]
饮水机2: 时间2的同学(0→2) + 时间3的同学(2→5) = 完成时间[2,5]
总时间 = 1+2+3+5 = 11
✅️问题解决方案
方案一:贪心算法 + 优先队列(推荐)
核心思想:
- 将同学按打水时间从小到大排序(短任务优先)
- 使用小根堆维护r台饮水机的完成时间
- 每次将下一个同学分配给最早空闲的饮水机
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class WaterDispenser {
public:
static int minimumTotalTime(int n, int r, vector<int>& times) {
// 贪心策略:短任务优先
sort(times.begin(), times.end());
// 使用小根堆维护每台饮水机的完成时间
priority_queue<int, vector<int>, greater<int>> machines;
// 初始化r台饮水机,完成时间都为0
for (int i = 0; i < r; i++) {
machines.push(0);
}
int totalTime = 0;
// 为每个同学分配饮水机
for (int i = 0; i < n; i++) {
// 取出最早完成的饮水机
int earliestFinishTime = machines.top();
machines.pop();
// 计算该同学的完成时间
int currentFinishTime = earliestFinishTime + times[i];
totalTime += currentFinishTime;
// 更新饮水机状态
machines.push(currentFinishTime);
}
return totalTime;
}
};
int main() {
int n, r;
cin >> n >> r;
vector<int> times(n);
for (int i = 0; i < n; i++) {
cin >> times[i];
}
cout << WaterDispenser::minimumTotalTime(n, r, times) << endl;
return 0;
}
方案二:动态规划优化版本
适用于数据规模较大的情况:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class WaterDispenserDP {
public:
static int minimumTotalTimeDP(int n, int r, vector<int>& times) {
sort(times.begin(), times.end());
// dp[i][j] = 前i个同学使用j台饮水机的最小总时间
vector<vector<int>> dp(n + 1, vector<int>(r + 1, INT_MAX));
// 初始化:0个同学使用任意台饮水机的时间为0
for (int j = 0; j <= r; j++) {
dp[0][j] = 0;
}
// 前缀和数组,用于快速计算连续同学使用同一台饮水机的总时间
vector<int> prefixSum(n + 1, 0);
vector<int> prefixTimeSum(n + 1, 0);
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + times[i-1] * i;
prefixTimeSum[i] = prefixTimeSum[i-1] + times[i-1];
}
// 填充DP表
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= min(i, r); j++) {
if (j == 1) {
// 只有一台饮水机,所有同学排队
dp[i][1] = prefixSum[i];
} else {
// 尝试将前k个同学分配给前j-1台饮水机
for (int k = j-1; k < i; k++) {
if (dp[k][j-1] != INT_MAX) {
int lastMachineTime = 0;
for (int l = k + 1; l <= i; l++) {
lastMachineTime += times[l-1] * (l - k);
}
dp[i][j] = min(dp[i][j], dp[k][j-1] + lastMachineTime);
}
}
}
}
}
return dp[n][r];
}
};
方案三:模拟法(便于理解)
直观的模拟实现,适合调试和理解:
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class WaterDispenserSimulation {
private:
struct Event {
int time;
int machineId;
int studentId;
bool operator<(const Event& other) const {
if (time != other.time) return time < other.time;
return machineId < other.machineId;
}
};
public:
static int minimumTotalTimeSimulation(int n, int r, vector<int>& times) {
// 创建(时间,原始索引)对并排序
vector<pair<int, int>> students;
for (int i = 0; i < n; i++) {
students.push_back({times[i], i});
}
sort(students.begin(), students.end());
// 事件队列和饮水机状态
set<Event> events;
vector<int> machineStatus(r, 0); // 每台饮水机的下次空闲时间
vector<int> completionTime(n); // 每个同学的完成时间
// 为每个同学分配饮水机
for (int i = 0; i < n; i++) {
int drinkTime = students[i].first;
int studentId = students[i].second;
// 找到最早空闲的饮水机
int bestMachine = 0;
for (int j = 1; j < r; j++) {
if (machineStatus[j] < machineStatus[bestMachine]) {
bestMachine = j;
}
}
// 分配该同学到最早空闲的饮水机
int startTime = machineStatus[bestMachine];
int finishTime = startTime + drinkTime;
completionTime[studentId] = finishTime;
machineStatus[bestMachine] = finishTime;
}
// 计算总时间
int totalTime = 0;
for (int i = 0; i < n; i++) {
totalTime += completionTime[i];
}
return totalTime;
}
};
方案四:优化输入输出版本
针对竞赛环境的高效实现:
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int main() {
int n, r;
scanf("%d %d", &n, &r);
int times[300];
for (int i = 0; i < n; i++) {
scanf("%d", ×[i]);
}
// 快速排序
sort(times, times + n);
// 使用数组模拟小根堆(当r较小时更高效)
int machines[100];
for (int i = 0; i < r; i++) {
machines[i] = 0;
}
int totalTime = 0;
for (int i = 0; i < n; i++) {
// 找到最小值的位置
int minPos = 0;
for (int j = 1; j < r; j++) {
if (machines[j] < machines[minPos]) {
minPos = j;
}
}
// 更新该饮水机的完成时间
machines[minPos] += times[i];
totalTime += machines[minPos];
}
printf("%d\n", totalTime);
return 0;
}
✅️问题延伸
1. 算法复杂度分析
时间复杂度:
- 排序:O(n log n)
- 主循环:O(n log r)(使用优先队列)或O(n × r)(使用数组)
- 总体:O(n log n + n log r)
空间复杂度:O®
优化技巧:
// 当r较小时,直接使用数组比优先队列更快
class OptimizedSolver {
public:
static int solve(int n, int r, vector<int>& times) {
sort(times.begin(), times.end());
if (r >= 100) {
// r较大时使用优先队列
return solveLargeR(n, r, times);
} else {
// r较小时使用数组
return solveSmallR(n, r, times);
}
}
private:
static int solveSmallR(int n, int r, vector<int>& times) {
vector<int> machines(r, 0);
int totalTime = 0;
for (int time : times) {
int minPos = min_element(machines.begin(), machines.end()) - machines.begin();
machines[minPos] += time;
totalTime += machines[minPos];
}
return totalTime;
}
static int solveLargeR(int n, int r, vector<int>& times) {
priority_queue<int, vector<int>, greater<int>> pq;
for (int i = 0; i < r; i++) pq.push(0);
int totalTime = 0;
for (int time : times) {
int earliest = pq.top();
pq.pop();
pq.push(earliest + time);
totalTime += earliest + time;
}
return totalTime;
}
};
2. 变种问题扩展
问题变种1:最小化最大完成时间
// 目标:最小化所有同学中最晚完成的时间(makespan)
int minimizeMaxCompletionTime(int n, int r, vector<int>& times) {
sort(times.begin(), times.end());
vector<int> machines(r, 0);
for (int time : times) {
int minPos = min_element(machines.begin(), machines.end()) - machines.begin();
machines[minPos] += time;
}
return *max_element(machines.begin(), machines.end());
}
问题变种2:考虑移动时间
// 同学在饮水机之间移动需要额外时间
struct Position {
int x, y;
int distance(const Position& other) {
return abs(x - other.x) + abs(y - other.y);
}
};
int solveWithMovement(int n, int r, vector<int>& times,
vector<Position>& students,
vector<Position>& machines) {
// 需要考虑分配时的移动成本
// 这将变成一个更复杂的分配问题
}
3. 多目标优化
Mermaid流程图 - 算法决策流程:
✅️问题预测
1. 数据规模扩展问题
大数据量优化:
// 当n很大时的分块处理
class LargeScaleSolver {
public:
static long long solveLarge(int n, int r, vector<int>& times) {
sort(times.begin(), times.end());
// 使用更高效的数据结构
if (n > 10000) {
return solveWithSegmentTree(n, r, times);
} else {
return solveStandard(n, r, times);
}
}
private:
static long long solveWithSegmentTree(int n, int r, vector<int>& times) {
// 使用线段树维护饮水机状态,支持快速查询和更新
// 时间复杂度:O(n log r)
// 实现略...
return 0;
}
};
2. 内存限制问题
空间优化版本:
// 当内存受限时的优化
int solveMemoryOptimized(int n, int r) {
// 不存储完整数组,在线处理
vector<int> machines(r, 0);
long long totalTime = 0;
// 先读取所有时间并排序
vector<int> times(n);
for (int i = 0; i < n; i++) {
cin >> times[i];
}
sort(times.begin(), times.end());
// 在线处理
for (int time : times) {
int minPos = 0;
for (int j = 1; j < r; j++) {
if (machines[j] < machines[minPos]) {
minPos = j;
}
}
machines[minPos] += time;
totalTime += machines[minPos];
}
return totalTime;
}
3. 实时动态问题
支持动态添加同学:
class DynamicWaterDispenser {
private:
priority_queue<int, vector<int>, greater<int>> machines;
long long currentTotal;
public:
DynamicWaterDispenser(int r) : currentTotal(0) {
for (int i = 0; i < r; i++) {
machines.push(0);
}
}
void addStudent(int drinkTime) {
int earliest = machines.top();
machines.pop();
int finishTime = earliest + drinkTime;
currentTotal += finishTime;
machines.push(finishTime);
}
long long getTotalTime() const {
return currentTotal;
}
};
✅️小结
算法核心:使用贪心策略 + 优先队列实现最优任务调度
关键洞察:
- 短任务优先:将打水时间短的同学优先安排,减少后续同学的等待时间
- 负载均衡:始终选择最早空闲的饮水机,保持各饮水机负载相对均衡
- 最优子结构:每一步的局部最优选择导致全局最优解
实现要点:
- 排序:O(n log n),为贪心策略做准备
- 优先队列:O(log r),高效维护饮水机状态
- 累计求和:计算每个同学的完成时间并累加
性能对比:
- 时间复杂度:O(n log n + n log r)
- 空间复杂度:O®
- 适用范围:n ≤ 300, r ≤ 100时性能优异
竞赛技巧:
- 当r较小时,用数组代替优先队列更快
- 注意数据类型,防止整数溢出
- 可以预处理排序,在线处理查询
这道题完美展现了贪心算法在任务调度中的应用,通过合理的策略选择实现了最优的时间分配方案。
希望如上措施及解决方案能够帮到有需要的你。
PS:如若遇到采纳如下方案还是未解决的同学,希望不要抱怨&&急躁,毕竟影响因素众多,我写出来也是希望能够尽最大努力帮助到同类似问题的小伙伴,即把你未解决或者产生新Bug黏贴在评论区,我们大家一起来努力,一起帮你看看,可以不咯。
若有对当前Bug有与如下提供的方法不一致,有个不情之请,希望你能把你的新思路或新方法分享到评论区,一起学习,目的就是帮助更多所需要的同学,正所谓「赠人玫瑰,手留余香」。
🧧🧧 文末福利,等你来拿!🧧🧧
如上问题有的来自我自身项目开发,有的收集网站,有的来自读者…如有侵权,立马删除。再者,针对此专栏中部分问题及其问题的解答思路或步骤等,存在少部分搜集于全网社区及人工智能问答等渠道,若最后实在是没能帮助到你,还望见谅!并非所有的解答都能解决每个人的问题,在此希望屏幕前的你能够给予宝贵的理解,而不是立刻指责或者抱怨!如果你有更优解,那建议你出教程写方案,一同学习!共同进步。
ok,以上就是我这期的Bug修复内容啦,如果还想查找更多解决方案,你可以看看我专门收集Bug及提供解决方案的专栏《全栈Bug调优(实战版)》,都是实战中碰到的Bug,希望对你有所帮助。到此,咱们下期拜拜。
码字不易,如果这篇文章对你有所帮助,帮忙给 bug菌 来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!
🫵 Who am I?
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-