[LeetCode每日一题] 1723. Find Minimum Time to Finish All Jobs

358 阅读2分钟

这题是一月份某次周赛的最后一题,记得当时提交了好多次都没过。题目本质就是将n个数分成k份,找出和最大的那份。然后问怎么分使得和最大的那份在所有分法中最小。

解法一:动态规划+状态压缩。

令dp[i][j]表示前[0,i]个工人完成任务集j的最小最大工作时间。任务集j就是j的二进制数中bit为1的那些任务。举个例子,有3个任务,则共有23=82^3=8种任务集,对于任务集5,5的二进制表示为101,则表示选择了任务1和任务3,即任务集5表示{任务1,任务3}.该题中任务数最多为12,因此最多有212=40962^{12}=4096种任务集。要计算dp[i][j],假设第i个工人完成工作集t,则dp[i][j]=max(dp[i-1][j-t],total[t]),我们选取令dp[i][j]最小的t即可.时间复杂度O(k2n)O(k·2^n)

var minimumTimeRequired = function(jobs, k) {
    let n = jobs.length
    const inf = 9999999999
    let dp = new Array(k).fill(null).map(d=>new Array(1<<n).fill(inf))
    // 预处理一下,计算任务集j所对应的所有任务需要完成的时间
    let total = [0]
    for (let j = 1; j < (1<<n); j++) {
        for (let t = 0; t < n; t++) {
            if ((j&(1<<t))!=0) {
                total[j] = total[j-(1<<t)] + jobs[t]
                break
            }
        }
    }
    for (let j = 0; j < (1<<n); j++) dp[0][j] = total[j]
    for (let i = 1; i < k; i++) {
        for (let j = 0; j < (1<<n); j++) {
            // 要计算dp[i][j],假设第i个工人完成工作集t,则dp[i][j]=max(dp[i-1][j-t],total[t]),我们选取令dp[i][j]最小的t即可
            for (let t = 0; t <= j; t++) {
                // 判断t是不是j的子集
                if ((t|j)==j) {
                    dp[i][j] = Math.min(dp[i][j], Math.max(dp[i-1][j-t], total[t]))
                }
            }
        }
    }
    return dp[k-1][(1<<n)-1]
};

参考官方答案,上面对于t的遍历还能优化成如下写法。其中(t-1)&j就是找到小于t的j的最大子集。

for (let t = j; t > 0; t = (t-1)&j) {
    dp[i][j] = Math.min(dp[i][j], Math.max(dp[i-1][j-t], total[t]))
}

解法二:二分+回溯+剪枝

上面解法是在我对题目有印象时想到的,如果是第一次遇到这题很难会想到上面解法。对于这种找最值的题,我们往往可以考虑使用二分法,即对于一个值limit,我们去检查当限定m时,是否存在这样一种分法使得最大的那个不超过limit。所以现在问题转变为check if valid了。我们使用回溯来check是否有效,定义一个数组workloads,workloads[i]表示第i个工人分配到的工作量,然后进行遍历+回溯。同时我们加上剪枝来进行优化。参考力扣官方答案

var minimumTimeRequired = function(jobs, k) {
    let left = Math.max(...jobs), right = jobs.reduce((a,b)=>a+b)
    while (left<right) {
        const limit = (left+right)>>1
        if (check(limit)) right = limit
        else left = limit+1
    }
    return left

    function check(limit) {
        let workloads = new Array(k).fill(0)
        return dfs(0)

        function dfs(idx) {
            if (idx >= jobs.length) return true
            for (let i = 0; i < k; i++) {
                if (workloads[i]+jobs[idx]<=limit) {
                    workloads[i] += jobs[idx]
                    if (dfs(idx+1)) return true
                    workloads[i] -= jobs[idx]
                }
                if (workloads[i] == 0) break
            }
            return false
        }
    }
};