题目描述
为了提升软件编码能力,小王制定了刷题计划,他选了题库中的n道题,编号从0到n-1,并计划在m天内按照题目编号顺序刷完所有的题目(注意,小王不能用多天完成同一题)。
在小王刷题计划中,小王需要用tme[i]的时间完成编号 i 的题目。
此外,小王还可以查看答案,可以省去该题的做题时间。为了真正达到刷题效果,小王每天最多直接看一次答案。
我们定义m天中做题时间最多的一天耗时为T(直接看答案的题目不计入做题总时间)。
请你帮小王求出最小的T是多少。
输入描述
第一行输入为time,time[i]的时间完成编号 i 的题目
第二行输入为m,m表示几天内完成所有题目,1 ≤ m ≤ 180
输出描述
最小耗时整数T
** 示例1
输入
999,999,999
4
输出
0
说明>
在前三天中,小王每天都直接看答案,这样他可以在三天内完成所有的题目并不花任何时间
** 示例2
输入
1,2,2,3,5,4,6,7,8
5
输出
4
说明
第一天完成前3题,第3题看答案;
第二天完成第4题和第5题,第5题看答案;
第三天完成第6和第7题,第7提看答案;
第四天完成第8题,直接看答案:
第五天完成第9题,直接看答案
思路
1. 问题转化
目标是找到最小的单日最大耗时T,使得能将n道题按顺序分成m段,每段可跳过1道耗时最大的题,剩余总耗时≤T。
2. 二分查找(确定最小T)
- 边界:左边界为0(极端情况每天跳过所有题),右边界为所有题目耗时总和(极端情况不跳过任何题);
- 逻辑:每次取中点mid作为候选T,验证是否可行;可行则尝试更小T(缩右边界),不可行则增大T(扩左边界),最终找到最小可行T。
3. 贪心验证(判断T是否可行)
- 遍历题目,维护当前段的「总耗时sum」和「段内最大耗时max_t」;
- 若
sum - max_t > T(去掉段内最大耗时题后仍超T),则开启新段,重置sum和max_t为当前题耗时; - 最终判断所需天数≤m即可(提前终止:天数超m直接返回不可行)。
4. 边界优化
若题目数≤天数(n≤m),每天做1题并跳过,直接返回T=0。
时空复杂度
- 时间:O(n log S)(n为题目数,S为总耗时,log S≤30);
- 空间:O(1)(仅常量空间)。
代码
function solution() {
// 读取输入
const time = readline().split(',').map(Number);
const m = Number(readline());
const n = time.length;
// 边界情况:题目数≤天数,每天做1题且跳过,总耗时0
if (n <= m) {
console.log(0);
return;
}
// 二分查找边界:左=0,右=所有题目总耗时
let left = 0;
let right = time.reduce((a, b) => a + b, 0);
let answer = right; // 初始化为最大值
// 核心:贪心验证函数(判断T是否可行)
const canFinish = (T) => {
let curDay = 1; // 已用天数(至少1天)
let sum = 0; // 当前段总耗时
let max_t = 0; // 当前段最大耗时(用于跳过)
for (const cost of time) {
sum += cost;
max_t = Math.max(max_t, cost);
// 关键:去掉当前段最大耗时后仍超T → 必须开新段
if (sum - max_t > T) {
curDay++;
sum = cost; // 新段从当前题开始
max_t = cost; // 新段最大耗时初始化
// 提前终止:天数超m直接返回不可行
if (curDay > m) return false;
}
}
return curDay <= m;
};
// 二分查找最小T
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (canFinish(mid)) {
answer = mid; // 记录可行的T
right = mid - 1; // 尝试更小的T
} else {
left = mid + 1; // T太小,需增大
}
}
console.log(answer);
}
// 测试用例(覆盖常规场景+反例)
const cases = [
// 示例1
`999,999,999
4`,
// 示例2
`1,2,2,3,5,4,6,7,8
5`,
// 反例:[5,1,1,1,1] m=2 → 输出2
`5,1,1,1,1
2`
];
// 模拟readline函数
let caseIndex = 0, lineIndex = 0;
const readline = (() => {
let lines = [];
return () => {
if (!lineIndex) lines = cases[caseIndex].trim().split('\n').map(l => l.trim());
return lines[lineIndex++];
};
})();
// 执行测试
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
console.log('-------');
});

783

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



