题目:
给你一个整数数组 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,最终的动态规划方程为: