华为OD机试(C卷+D卷)2024真题目录(Java & c++ & python)
题目描述
项目组共有 N 个开发人员,项目经理接到了 M 个独立的需求,每个需求的工作量不同,且每个需求只能由一个开发人员独立完成,不能多人合作。
假定各个需求直接无任何先后依赖关系,请设计算法帮助项目经理进行工作安排,使整个项目能用最少的时间交付。
输入描述
第一行输入为 M 个需求的工作量,单位为天,用逗号隔开。
例如:
X1 X2 X3 … Xm
表示共有 M 个需求,每个需求的工作量分别为X1天,X2天,…,Xm天。其中:
0 < M < 30
0 < Xm < 200
第二行输入为项目组人员数量N
例如:
5
表示共有5名员工,其中 0 < N < 10
输出描述
最快完成所有工作的天数
例如:
25
表示最短需要25天完成所有工作
用例
输入
6 2 7 7 9 3 2 1 3 11 4
2
输出
28
说明 共有两位员工,其中一位分配需求 6 2 7 7 3 2 1 共需要28天完成,另一位分配需求 9 3 11 4 共需要27天完成,故完成所有工作至少需要28天。
解题思路
二分法+回溯。
二分法贴了以下链接以供学习,如果对二分足够熟练可跳过。
二分法学习链接
回归正题,这个题可以转化为
- M个独立需求 → M个球
- N个开发人员 → N个桶
现在要将M个球装入N个桶中,问桶的最小容量是多少?
对于桶的容量可以从max(Xi) 遍历到 sum(Xi),第一个满足的则为答案,可以发现这是一个线性增长的数值,所以可以用二分法来优化。
首先确定左右边界:
- left: 如果桶非常多,满足一个球一个桶,那么此时桶容量至少应该是max(X1, X2, … , Xm)
- right: 如果桶非常少,比如只有一个桶,那么此时所有球都需要装入该桶,则桶容量至多是 sum(X1, X2, …, Xm)
如何去校验当前mid=(left+right)/2 能否满足条件?
可以观察到数据范围较小,可以直接回溯暴力遍历所有情况,遇到第一个满足的情况即可退出。
如果装不下则说明容量至少要大于mid,否则说明mid也可以满足。
C++、Java、Python 代码如下:
C++参考代码
#include <bits/stdc++.h>
using namespace std;
vector<int> balls; // 保存所有球的体积
int n; // 桶的数量
/*!
* @brief 检查是否可以将所有球装入 n 个容量为 limit 的桶中
* @param index 当前要被装入的球的索引(balls 数组索引)
* @param buckets 桶数组,buckets[i] 记录的是第 i 个桶已使用的容量
* @param limit 每个桶的最大容量
* @return 如果可以装下所有球,返回 true;否则返回 false
*/
bool canFit(int index, int buckets[], int limit) {
// 如果所有球都已经处理完,则表示成功
if (index == balls.size()) return true;
// 当前要装入的球的体积
int currentBall = balls[index];
// 尝试将当前球放入每个桶中
for (int i = 0; i < n; i++) {
// 剪枝优化:如果当前桶和前一个桶的已使用容量相同,则跳过
if (i > 0 && buckets[i] == buckets[i - 1]) continue;
// 如果当前桶可以装下这个球
if (currentBall + buckets[i] <= limit) {
// 将球放入桶中
buckets[i] += currentBall;
// 递归检查下一个球
if (canFit(index + 1, buckets, limit)) return true;
// 回溯:如果无法成功,则撤回之前的操作
buckets[i] -= currentBall;
}
}
// 如果无法找到合适的放置方式,则返回 false
return false;
}
/*!
* @brief 求解最小的桶容量,使得 n 个桶可以装下所有球
* @return 最小的桶容量
*/
int findMinimumCapacity() {
// 对球的体积进行降序排序,有助于降低后续回溯操作的复杂度
sort(balls.begin(), balls.end(), greater<int>());
// 二分查找的初始范围:low 是最大的球的体积,high 是所有球体积的总和
int low = balls[0];
int high = accumulate(balls.begin(), balls.end(), 0);
// 记录最优解
int optimalCapacity = high;
// 二分查找最小桶容量
while (low <= high) {
int mid = (low + high) / 2;
// 初始化桶的使用情况
int buckets[n] = {0};
// 如果容量为 mid 的 n 个桶可以装下所有球
if (canFit(0, buckets, mid)) {
// 尝试寻找更小的可能解
optimalCapacity = mid;
high = mid - 1;
} else {
// 增加桶容量
low = mid + 1;
}
}
return optimalCapacity;
}
int main() {
int num;
while (cin >> num) {
balls.emplace_back(num);
}
// 最后一个输入的数字是桶的数量
n = balls.back();
balls.pop_back();
// 输出最小的桶容量
cout << findMinimumCapacity() << endl;
return 0;
}
Java参考代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static Integer[] balls; // 用于存储所有球的体积
static int n; // 桶的数量
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取球的体积并存入数组中
balls = Arrays.stream(sc.nextLine().split(" "))
.map(Integer::parseInt)
.toArray(Integer[]::new);
// 读取桶的数量
n = Integer.parseInt(sc.nextLine());
// 输出结果
System.out.println(getResult());
}
/**
* @return 最小的桶容量,使得 n 个桶可以装下所有球
*/
public static int getResult() {
// 对球的体积进行降序排序,有助于降低后续回溯操作的复杂度
Arrays.sort(balls, (a, b) -> b - a);
// 初始化二分查找的范围
int min = balls[0]; // 最小容量至少要能装下最大的球
int max = Arrays.stream(balls).reduce(Integer::sum).get(); // 当只有一个桶时,该桶容量需要装下所有球的总和
// 记录最优解
int ans = max;
// 二分查找最小的桶容量
while (min <= max) {
int mid = (min + max) >> 1;
// 检查容量为 mid 的桶是否可以装下所有球
if (canFitAllBalls(0, new int[n], mid)) {
// 如果可以,尝试更小的容量
ans = mid;
max = mid - 1;
} else {
// 如果不行,增加桶的容量
min = mid + 1;
}
}
return ans;
}
/**
* 检查是否可以用 n 个容量为 limit 的桶装下所有球
*
* @param index 当前要被装入的球的索引(balls 数组索引)
* @param buckets 桶数组,buckets[i] 记录的是第 i 个桶已使用的容量
* @param limit 每个桶的最大容量
* @return 如果可以装下所有球,返回 true;否则返回 false
*/
public static boolean canFitAllBalls(int index, int[] buckets, int limit) {
// 如果所有球都已经装入桶中,则返回 true
if (index == balls.length) return true;
// 当前要装入的球的体积
int currentBall = balls[index];
// 尝试将当前球放入每个桶中
for (int i = 0; i < buckets.length; i++) {
// 剪枝优化:如果当前桶和前一个桶的已使用容量相同,则跳过
if (i > 0 && buckets[i] == buckets[i - 1]) continue;
// 如果当前桶可以装下这个球
if (currentBall + buckets[i] <= limit) {
// 将球放入桶中
buckets[i] += currentBall;
// 递归检查下一个球
if (canFitAllBalls(index + 1, buckets, limit)) return true;
// 回溯:如果无法成功,则撤回之前的操作
buckets[i] -= currentBall;
}
}
// 如果无法找到合适的放置方式,则返回 false
return false;
}
}
Python参考代码
# 输入获取:球的体积列表
balls = list(map(int, input().split()))
# 输入获取:桶的数量
n = int(input())
def canFitAllBalls(index, buckets, limit):
"""
检查是否可以用 n 个容量为 limit 的桶装下所有球
:param index: 当前轮次要被装入的球的索引(balls 数组索引)
:param buckets: 桶数组,buckets[i] 记录的是第 i 个桶已使用的容量
:param limit: 每个桶的最大容量,即限制
:return: 如果 k 个容量为 limit 的桶可以装下所有 balls,返回 True;否则返回 False
"""
# 如果所有球都已经装入桶中,则返回 True
if index == len(balls):
return True
# 当前要装入的球的体积
selected = balls[index]
# 遍历所有桶,尝试将当前球放入
for i in range(len(buckets)):
# 剪枝优化:如果当前桶和前一个桶的已使用容量相同,则跳过
if i > 0 and buckets[i] == buckets[i - 1]:
continue
# 如果当前桶可以装下这个球
if selected + buckets[i] <= limit:
# 将球放入桶中
buckets[i] += selected
# 递归检查下一个球
if canFitAllBalls(index + 1, buckets, limit):
return True
# 回溯:如果无法成功,则撤回之前的操作
buckets[i] -= selected
# 如果无法找到合适的放置方式,则返回 False
return False
# 对球的体积进行降序排序,有助于降低后续回溯操作的复杂度
balls.sort(reverse=True)
# 二分查找的范围:即每个桶的容量最小值和最大值
low = balls[0] # 最小容量至少要能装下最大的球
high = sum(balls) # 当只有一个桶时,该桶容量需要装下所有球的总和
# 记录最优解
ans = high
# 二分查找最小的桶容量
while low <= high:
mid = (low + high) >> 1
# 检查容量为 mid 的桶是否可以装下所有球
if canFitAllBalls(0, [0] * n, mid):
# 如果可以,尝试更小的容量
ans = mid
high = mid - 1
else:
# 如果不行,增加桶的容量
low = mid + 1
# 输出最小的桶容量
print(ans)