leetcode 1723.完成所有工作的最短时间 - 二分+递归
题干
给你一个整数数组 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 。
提示:
1 <= k <= jobs.length <= 12
1 <= jobs[i] <= 107
知识点&算法
还就那个暴力递归
只有12的数据量…想都不想,一上来就是一手暴力递归,然后直接超时,甚至超过五个元素的样例都过不了
class Solution {
public:
int minSum = 0x3f3f3f3f,k;
void recur(vector<int> vis,vector<int>& jobs,int preMaxSum,int currentSum,int cnt,int unpickedCnt){
int n = jobs.size(),check = 0;
if(unpickedCnt > n) return;
for(int i = 0 ; i < n ; ++i) check += vis[i];
if(check == n){
if(cnt == k) minSum = min(minSum,preMaxSum);
return;
}
for(int i = 0 ; i < n ; ++i){
if(vis[i] == 0){
//选进当前组并开始选下一个人
currentSum += jobs[i];
vis[i] = 1;
recur(vis,jobs,max(preMaxSum,currentSum),0,cnt+1,unpickedCnt);
//选进当前组并继续当前人
recur(vis,jobs,preMaxSum,currentSum,cnt,unpickedCnt);
//不选当前组并开始下个人
vis[i] = 0;
if(currentSum != 0) recur(vis,jobs,max(preMaxSum,currentSum),0,cnt+1,unpickedCnt+1);
//不选当前组并继续当前人
recur(vis,jobs,preMaxSum,currentSum,cnt,unpickedCnt+1);
}
}
return;
}
int minimumTimeRequired(vector<int>& jobs, int k) {
this->k = k;
vector<int> vis(jobs.size(),0);
recur(vis,jobs,0,0,0,0);
return minSum;
}
};
二分优化下的递归
二分的核心,因为对于我们要求的最小答案,在样例中一定存在可行的大于最小答案的情形,所以可以利用二分搜索来查找可行的最大工作时间。
为了缩小二分的范围,左界设为单个工作中时间最长的,右界设为所有工作的时间总和。
对于搜索过程中的每个工作时间,我们对其进行检查,如果可行就左移右界,查找更小的答案,如果不可行就右移左界,查找更大的答案。
以递归的方式检查当前最大工作时间是否可行。遍历所有工作,并不断回溯分配给每个工人,直到全部分配完毕。
由于分配工作后不能超出当前检查的最大工作时间,超出即不能分配。
题解
二分优化+剪枝递归
剪枝中占比最大的还就是那句workloads[i] == 0
class Solution {
public:
bool recur(vector<int>& jobs,vector<int>& workloads,int maxWorkload,int idxOfJob){
if(idxOfJob >= jobs.size() ) return true;
int n = workloads.size(),job = jobs[idxOfJob];
for(int i = 0 ; i < n ; ++i){
if(workloads[i] + job <= maxWorkload){
//可分配的情况
workloads[i] += job;
//不断递归分配能够满足条件的状况
if(recur(jobs,workloads,maxWorkload,idxOfJob+1) ) return true;
//不满足条件,回溯
workloads[i] -= job;
}
//不满足上述条件,即不可分配,或当前可分配但此后无法分配
//workloads[i] == 0当前工作被分配到一个工人后后续无法分配,由于初状态是0,意味着这个工作不论分配到哪个工人,后续都无法产生可分配的策略;workloads[i] + jobs[...] == maxWorkload的情况为当前可分配但后续出现了不可再分的情况
if(workloads[i] == 0 || workloads[i] + job == maxWorkload) break;
}
return false;
}
bool check(int workers,vector<int>& jobs,int maxWorkload){
vector<int> workloads(workers,0);
return recur(jobs,workloads,maxWorkload,0);
}
int minimumTimeRequired(vector<int>& jobs, int k) {
sort(jobs.begin(),jobs.end(),[](int a,int b){
return a > b;
});
int n = jobs.size(),sum = 0;
for(int i = 0 ; i < n ; ++i) sum += jobs[i];
int l = jobs[0],r = sum;
//二分法寻找一个可能的最大工作时间
//如果一个最大工作时间存在,则大于他的工作时间一定存在,所以往更小处找
//m在可行时,为了往更小处找,r = m ,不可行时往更大处找l = m + 1(m已经不可行)
while(l < r){
int m = (l + r) >> 1;
if(check(k,jobs,m)){
//cout<<l<<' '<<r<<' '<<m<<endl;
r = m;
}else{
l = m + 1;
}
}
return l;
}
};