华为OD 2024笔试机试 - 项目排期 (Java/c++/python C卷D卷真题算法)

华为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)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值