问题:
将一组jobs分配给k个人,求完成这些job,最小需要的时间。
Example 1: Input: jobs = [3,2,3], k = 3 Output: 3 Explanation: By assigning each person one job, the maximum time is 3. Example 2: Input: jobs = [1,2,4,7,8], k = 2 Output: 11 Explanation: Assign the jobs the following way: Worker 1: 1, 2, 8 (working time = 1 + 2 + 8 = 11) Worker 2: 4, 7 (working time = 4 + 7 = 11) The maximum working time is 11. Constraints: 1 <= k <= jobs.length <= 12 1 <= jobs[i] <= 107
解法:Backtracking(回溯算法)DP(动态规划)
- 状态:到当前job[pos]为止,job的分配情况path,以及当前需要的最大工数preMax
- 选择:将当前job[pos]分配给 0~k-1 任意一个工人
- 除去,这个工人当前所用工数,和已经选择过的工人相同。(这样的选择,没什么区别)
- or,这个工人的工数+当前job[pos]的工数>res,超过已经成功分配得到的一个解。(我们要找更小的解)
- 递归退出条件:pos==jobs.size
♻️ 优化:将jobs进行从大到小排序,先分配工数大的,会比较快。
相较于将 res 初始化为INT_MAX,更优化的方法:取最大的jobs.size/k个工作给一个工人,这个工人用时一定最大。
代码参考:
1 class Solution { 2 public: 3 //state: So far, jobs assignment situation:path, and the max workLoad: preMax 4 //jobs[pos] assign to who? 5 //opt:workers[0]~wokers[k-1] 6 int res = 0; 7 int path[12]; 8 void backtrack(int pos, int preMax, vector<int>& jobs, int k) { 9 if(pos == jobs.size()) { 10 res = min(res, preMax); 11 return; 12 } 13 unordered_set<int> used; 14 for(int i=0; i<k; i++) { 15 if(used.insert(path[i]).second==false) continue;//same workload try only once 16 if(path[i]+jobs[pos]>=res) continue;// bigger than already gotten min res. 17 path[i]+=jobs[pos]; 18 backtrack(pos+1, max(preMax, path[i]), jobs, k); 19 path[i]-=jobs[pos]; 20 } 21 return; 22 } 23 int minimumTimeRequired(vector<int>& jobs, int k) { 24 if(k==jobs.size()) return *max_element(begin(jobs), end(jobs)); 25 vector<int> path(k, 0); 26 sort(jobs.begin(), jobs.end(), greater<int>()); 27 for (int i = 0; i < jobs.size(); i += k) 28 res += jobs[i]; 29 backtrack(0, 0, jobs, k); 30 return res; 31 } 32 };
DP:
状态:
dp[i][j]:i 个工人,做map=j的job,花费的最小工数。
状态转移:
将 i 个工人,分出一个人,去做 j 的所有子集合的可能,
例如其中一个子集合subj,那么这个人需要做的工数=sum[subj]
总工数:剩下i-1个人的工数dp[i-1][j-subj] , sum[subj] 中求最大。
那么,对于所有子集合可能,我们要求的最优解:min(所有可能):即:
dp[i][j] = min{ all subj: max( dp[i-1][j-subj], sum[subj] ) }
base case:
dp[1][j] = sum[j]
求sum[j]:
j为job的map选择:如果 j的某一位 i ==1,那么sum+=jobs[i],即:
for(all j : 0~(1<<n) ) i:第 i 个job
if ((j>>i)&1==1) sum[j]+=jobs[i]
代码参考:
1 class Solution { 2 public: 3 //dp[i][j]:0~i workers to do map_j jobs: the min time cost 4 //dp[i][j]=min( <for all subj> : max(dp[i-1][subj],sum[j-subj]) ) subj:subset of j 5 //one worker to do sum[j-subj] and others have the min time cost: dp[i-1][subj]; 6 7 //base case: dp[1][j]=sum[job[0]+...+job[j]]; 8 int minimumTimeRequired(vector<int>& jobs, int k) { 9 if(k==jobs.size()) return *max_element(begin(jobs), end(jobs)); 10 int n = jobs.size(); 11 vector<vector<int>> dp(k+1, vector<int>(1<<n)); 12 vector<int> sum((1<<n), 0); 13 //get sum: 14 for(int i=1; i<(1<<n); i++) { 15 for(int j=0; j<n; j++) { 16 if((i>>j)&1==1) { 17 sum[i] += jobs[j]; 18 } 19 } 20 } 21 //base: 22 for(int j=0; j<(1<<n); j++) { 23 dp[1][j] = sum[j]; 24 } 25 //loop: 26 for(int i=2; i<=k; i++) {//for every i workers 27 for(int j=1; j<(1<<n); j++) {//for each jobs assignment map:j 28 dp[i][j] = max(dp[i-1][j],sum[0]); 29 for(int subj=j; subj>0; subj=(subj-1)&j) {//for each subset of j:all->0 30 dp[i][j] = min(dp[i][j], max(dp[i-1][subj], sum[j-subj])); 31 } 32 } 33 } 34 35 return dp[k][(1<<n)-1]; 36 } 37 };