Leetcode之完成所有工作最短的时间

题目:

给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。

请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。

返回分配方案中尽可能 最小 的 最大工作时间 。

示例 1:

输入:jobs = [3,2,3], k = 3
输出:3
解释:给每位工人分配一项工作,最大工作时间是 3 。
示例 2:

输入:jobs = [1,2,4,7,8], k = 2
输出:11
解释:按下述方式分配工作:
1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)
2 号工人:4、7(工作时间 = 4 + 7 = 11)
最大工作时间是 11 。

代码:

方法一——状态压缩动态规划:

class Solution {
public:
    int minimumTimeRequired(vector<int>& jobs, int k) {
        int n = jobs.size();
        vector<int> tot(1 << n, 0);
        for (int i = 1; i < (1 << n); i++) {
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) == 0) continue;
                int left = (i - (1 << j));
                tot[i] = tot[left] + jobs[j];
                break;
            }
        }
        
        vector<vector<int>> dp(k, vector<int>(1 << n, -1));
        for (int i = 0; i < (1 << n); i++) {
            dp[0][i] = tot[i];
        }
        
        for (int j = 1; j < k; j++) {
            for (int i = 0; i < (1 << n); i++) {
                int minv = INT_MAX;
                for (int s = i; s; s = (s - 1) & i) { // 枚举 i 的全部子集
                    int left = i - s;
                    int val = max(dp[j-1][left], tot[s]);
                    minv = min(minv, val);
                }
                dp[j][i] = minv;
            }
        }
        return dp[k-1][(1<<n)-1];
    }
};

思路:

设jobs 的长度为 NN,则可以用一个 [0,2^N]之间的整数代表 jobs 的任意一个子集。下文中,我们不加区分地用 i 表示「整数 i」或者「整数 i 对应的子集」

于是,令 tot[i] 代表子集 i 的工作总时间。设子集 i 的其中(任意)一个元素为 j,则 i-(1<<j) 代表了「子集 i中去掉了元素 j 后剩下的那部分」。因此,我们有
tot[i]=tot[i−(1<<j)]+jobs[j]。

随后,我们设 dp[j][i] 表示:前 j 个工人为了完成作业子集 i,需要花费的最大工作时间的最小值。

由于前 j个工人完成了作业子集 i,因此第 j 个工人必然完成 i 的某个子集 s,而其余的工人完成子集 i-s。对于特定的 s 而言,前 j−1 个工人完成子集 i−s 需要花费的最大工作时间的最小值为 dp[j-1][i-s],而工人 jj 的工作时间为 tot[s],故此时总的最大工作时间的最小值为 max{dp[j−1][i−s],tot[s]}。

因此,根据题意,我们遍历所有的子集 s ,并求出全局的最小值,即
dp[j][i]= s⊆imin(max{dp[j−1][i−s],tot[s]})

最终,dp[k-1][(1<<N)-1]即为我们所求的答案。

方法二——状态压缩动态规划+二分搜索:

代码:

class Solution {
public:
    int minimumTimeRequired(vector<int>& jobs, int k) {
        int n = jobs.size();
        vector<int> tot(1 << n, 0);
        for (int i = 1; i < (1 << n); i++) {
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) == 0) continue;
                int left = (i - (1 << j));
                tot[i] = tot[left] + jobs[j];
                break;
            }
        }

        int l = *max_element(jobs.begin(), jobs.end());
        int r = accumulate(jobs.begin(), jobs.end(), 0);
        while (r > l) {
            int mid = (l + r) / 2;
            vector<int> dp(1 << n, INT_MAX / 2);
            dp[0] = 0;
            for (int i = 0; i < (1 << n); i++) {
                for (int s = i; s; s = (s - 1) & i) {
                    if (tot[s] <= mid) {
                        dp[i] = min(dp[i], dp[i-s] + 1);
                    }
                }
            }
            if (dp[(1<<n) - 1] <= k) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return l;
    }
};

思路:

任给定一个整数,我们可以求出当每个工人诶工作时间都不超过limit时,最少需要多少个工人,所需工人数量为limit的函数,不妨设为f(limit)。显然,limit越大,f(limit)越小。

由于工人数量为k,因此由f(limit)<=k。可以使用二分搜索的方法,找到满足f(limit)<k的最小Limit值,即为答案。二分搜索过程需要确定上下届,显然,limit的上界为所有jons[i]的和,limit的下届为jobs的最大值。

依然是状态压缩动态规划,设dp[i]是完成作业子集i最少需要的工人数量。对于子集i而言,假设其中一个工人完成了i的某个子集s,则需要满足tot[s]<=limit;若s满足这样的性质,则工人数量为dp[i-s]+1,最终的动态规划方程为:

                                                                                           

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值