题目
部门在进行需求开发时需要进行人力安排。
当前部门需要完成 N 个需求,需求用 requirements 表述,requirements[i] 表示第 i 个需求的工作量大小,单位:人月。
这部分需求需要在 M 个月内完成开发,进行人力安排后每个月人力是固定的。
目前要求每个月最多有2个需求开发,并且每个月需要完成的需求不能超过部门人力。
请帮助部门评估在满足需求开发进度的情况下,每个月需要的最小人力是多少?
输入描述
输入为 M 和 requirements,M 表示需求开发时间要求,requirements 表示每个需求工作量大小,N 为 requirements长度,
1 ≤ N/2 ≤ M ≤ N ≤ 10000
1 ≤ requirements[i] ≤ 10^9
输出描述
对于每一组测试数据,输出部门需要人力需求,行末无多余的空格
用例
输入 | 输出 | 说明 |
---|---|---|
3 3 5 3 4 | 6 | 输入数据两行, 第一行输入数据3表示开发时间要求, 第二行输入数据表示需求工作量大小, 输出数据一行,表示部门人力需求。 当选择人力为6时,2个需求量为3的工作可以在1个月里完成,其他2个工作各需要1个月完成。可以在3个月内完成所有需求。 当选择人力为5时,4个工作各需要1个月完成,一共需要4个月才能完成所有需求。 因此6是部门最小的人力需求。 |
思考
题目要求计算部门在开发时间 M 内需要的最小人力。每天需要的人力肯定不能小于当天需要的人力需求,对人力需求序列 requirements 从小到大排序,能确定人力最小边界 l。要求完成所有需求最大人力肯定不会大于所有需求人力之和,这是最大边界 r,那么问题就是在最小边界 l 和最大边界 r 中进行二分查找,找到最小能满足要求的人力。定义一个函数 canCompleteInM 接收一个每天的人力需求值作为参数计算是否能在规定时间 M 内完成所有需求的开发,如果能则把二分查找右边界左移,这时候 r = m而不是r = m-1,m -1 可能会遗漏解。canCompleteInM 函数计算人力需求 m 是否满足需求开发用到贪心策略和双指针,每次尝试将最大和最小的需求配对,这种策略能更有效地利用人力:
-
排序后,左指针指向最小需求,右指针指向最大需求
-
如果最小和最大需求可以配对,则一起完成
-
否则最大需求单独完成
这种策略能确保在相同人力下,使用最少的月数完成所有需求,从而得到正确的结果。
算法过程
-
排序需求列表:首先将需求按工作量从小到大排序,以便后续使用双指针法进行配对。
-
确定二分查找的边界:
-
左边界
left
设为需求中的最大值,因为每个月至少要能完成最大的那个需求。 -
右边界
right
设为所有需求的总和,这是极端情况下(每个月只完成一个需求)的人力上限。
-
-
二分查找最小人力值:
-
取中间值
mid
作为当前尝试的人力值。 -
检查在人力为
mid
的情况下,是否能在M
个月内完成所有需求。 -
如果可以,尝试更小的人力值(将右边界调整为
mid
)。 -
如果不可以,需要更大的人力值(将左边界调整为
mid + 1
)。
-
-
检查是否能在 M 个月内完成:
-
使用双指针法,左指针指向最小的需求,右指针指向最大的需求。
-
尝试将最大和最小的需求配对,如果它们的工作量之和不超过当前人力值,则可以在一个月内完成这两个需求。
-
如果无法配对,则最大的需求单独用一个月完成。
-
统计使用的月数,如果超过
M
则返回 false,否则返回 true。
-
这种方法的时间复杂度主要由排序操作决定,为 O (N log N),二分查找的时间复杂度为 O (log S),其中 S 是需求总和,每次检查的时间复杂度为 O (N),因此总的时间复杂度为 O (N log N + N log S),可以高效处理题目给定的输入范围。
参考代码
function solution() {
let M = parseInt(readline());
let requirements = readline()
.split(" ")
.map(Number)
.sort((a, b) => a - b);
const canCompleteInM = function (manpower) {
let months = 0;
let left = 0;
let right = requirements.length - 1;
// 使用双指针法,尝试将最大和最小的需求配对
while (left <= right) {
if (
left < right &&
requirements[left] + requirements[right] <= manpower
) {
// 如果最小和最大的需求可以在一个月内完成
left++;
right--;
} else {
// 否则最大的需求单独用一个月完成
right--;
}
months++;
// 如果使用的月数已经超过 M,返回 false
if (months > M) {
return false;
}
}
return true;
};
// 确定二分查找的左右边界
// 左边界是需求中的最大值(因为每个月至少要能完成最大的需求)
// 右边界是所有需求的总和(极端情况下每个月只完成一个需求)
let left = Math.max(...requirements);
let right = requirements.reduce((sum, req) => sum + req, 0);
// 二分查找最小的人力值
while (left < right) {
const mid = Math.floor((left + right) / 2);
// 检查在人力为 mid 的情况下是否能在 M 个月内完成所有需求
if (canCompleteInM(mid)) {
// 如果可以,尝试更小的人力值
right = mid;
} else {
// 如果不可以,需要更大的人力值
left = mid + 1;
}
}
console.log(left);
}
const cases = [
`3
3 5 3 4`,
];
let caseIndex = 0;
let lineIndex = 0;
const readline = (function () {
let lines = [];
return function () {
if (lineIndex === 0) {
lines = cases[caseIndex]
.trim()
.split("\n")
.map((line) => line.trim());
}
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
});