【OD机试题解法笔记】部门人力分配

题目


部门在进行需求开发时需要进行人力安排。
当前部门需要完成 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 是否满足需求开发用到贪心策略和双指针,每次尝试将最大和最小的需求配对,这种策略能更有效地利用人力:

  1. 排序后,左指针指向最小需求,右指针指向最大需求

  2. 如果最小和最大需求可以配对,则一起完成

  3. 否则最大需求单独完成

这种策略能确保在相同人力下,使用最少的月数完成所有需求,从而得到正确的结果。

算法过程

  1. 排序需求列表:首先将需求按工作量从小到大排序,以便后续使用双指针法进行配对。

  2. 确定二分查找的边界

    • 左边界 left 设为需求中的最大值,因为每个月至少要能完成最大的那个需求。

    • 右边界 right 设为所有需求的总和,这是极端情况下(每个月只完成一个需求)的人力上限。

  3. 二分查找最小人力值

    • 取中间值 mid 作为当前尝试的人力值。

    • 检查在人力为 mid 的情况下,是否能在 M 个月内完成所有需求。

    • 如果可以,尝试更小的人力值(将右边界调整为 mid)。

    • 如果不可以,需要更大的人力值(将左边界调整为 mid + 1)。

  4. 检查是否能在 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();
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值