leetcode 1723.完成所有工作的最短时间 - 二分+递归

17 篇文章 0 订阅
5 篇文章 0 订阅

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值