Leetcode---1723完成所有工作的最短时间

今天的每日一题比较难,看了很久没有什么思路,所以看了官方题解,好在看懂第一个方法了,下面放上代码和自己的理解。下午考完试再来研究dp的解法和其他大佬提出的解法。
在这里插入图片描述

思路

根据题目要求,是要在完成全部工作的前提下,找到工人最大工作时间的最小值,所以一个最先想到的方法就是不断枚举和尝试,把不同的工作分给不同的工人,然后在可以完成全部工作的方案中,找到每个方案最大值中的最小值(也就是把每个可以完成全部工作的方案中,工人最大工作时长拿出来进行比较,找到这些最大值中的最小值),此时,我们的目标变成了两个:

  1. 找到所有可以完成全部工作的分配方案;
  2. 找到方案中最大值中的最小值limit

在目标1中,为了减少不必要的计算,我们可以做一些“剪枝”。

  1. 因为每个工作都要分配到工人,所以limit的理论最小值为工作中时长最大的那一个;考虑到极端情况,所有的工作让一个人做,所以limit的理论最大值为所有工作时长的总和,因此limit的范围限制到了 [max(jobs), sum(jobs)]
  2. 因为是要找到所有满足要求的分配方案,所以要对所有可能的limit进行遍历,当 ( i + 1 ) ∈ [ 1 , k ] (i+1) \in [1,k] i+1[1,k] 时,如果工人i当前的工作时长超过limit,则后续的工人就不用再进行分配了,这样又减少了一部分的计算;
  3. 如何才是满足条件的分配方案呢?也就是在当前limit下,所有的工人都已经分配了工作,且每个人的工作时长均不大于limit,则证明当前的limit是合理的;
  4. 因为只要limit=n时满足要求,那么对于任意的limit>n,均满足条件,所以可以使用二分查找寻找最小的limit值,因此又减少了部分计算开销。

综上,可以得到如下的代码:

代码

class Solution {
    public int minimumTimeRequired(int[] jobs, int k) {
//因为先分配工作时间长的工作会方便后续的分配,为了方便操作,对数组进行逆序
        Arrays.sort(jobs);//默认的到的是升序
        int left=0,right=jobs.length-1;
        while(left<right)//如果不进行降序排序,也能得到准确的结果,也能ac,但是时间开销比较大
        {
            int temp = jobs[right];
            jobs[right] = jobs[left];
            jobs[left] = temp;
            left++;
            right--;
        }
        //为了减少不必要的计算,设置上限为所有工作之和,下限为时长最大的工作
        int l = jobs[0], r = Arrays.stream(jobs).sum();
        while(l<r)
        {
            int mid = (l+r)>>1;//因为使用二分查找,所以这里获取中间值
            if(check(jobs, mid, k))//如果把mid作为limit可以成立,那么对于limit>mid均成立
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
    public boolean check(int[] jobs, int limit, int k)
    {
        int[] workArrange = new int[k];//设置一个长度为工人数量的数组,用于存储每个工人对应的工作时长
        return backTrack(jobs, workArrange, 0 , limit);
    }
    public boolean backTrack(int[] jobs, int[] workArrange, int i, int limit)
    {
        if (i>=jobs.length)//满足条件的方案
            return true;
        int curWork = jobs[i];
        for(int j=0;j<workArrange.length;j++)
        {
            if(workArrange[j]+curWork <= limit)//对每个工人进行工作分配,如果满足条件则分配,不满足的话,后续的工人也不用分配工作了
            {
                workArrange[j] += curWork;
                if (backTrack(jobs,workArrange,i+1,limit))//如果已经是最后一个工人了,那么证明该方案满足条件
                    return true;
                workArrange[j] -= curWork;//经过上面的递归,发现把第i个工作分配给第j个人不满足条件,那么就不能分配给工人j工作i,所以删除
            }

            if (workArrange[j] == 0 || workArrange[j]+curWork == limit)
                break;
        }
        return false;
    }
}

------------------------分割线------------------------------------
来更新状态压缩的dp解法了,是在有些难理解。。。如果我的理解存在错误,请指正,先谢谢各位了!

思路

本方法的重点部分在于

for(int i=1;i<(1<<n);i++)
{
int x = Integer.numberOfTrailingZeros(i);
int y = i-(1<<x);
sum[i] = sum[y] + jobs[x];
}

具体的解释详见代码内的注释,结合输入案例的结果更加容易理解。

代码

class Solution {
    public int minimumTimeRequired(int[] jobs, int k)
    {
        int n = jobs.length;

        //使用状态压缩方法,因为对于n个工作而言,每个工作的状态只有两种——以被分配1 和 未被分配0,所以可以使用状态压缩辅助计算
        int[] sum = new int[1<<n];//因为状态压缩就是使用二进制数辅助计算,为了方便对每个工作的状态进行操作,这里设置了一个2的n次方的数组

        for(int i=1;i<(1<<n);i++)
        {
            //返回i的二进制形式中最后一个1之后0的数量,如果i为0,则返回32
            int x = Integer.numberOfTrailingZeros(i);//x得到的是当前的分配方案于上一个分配方案之间所缺少的工作的数组索引值,如当前的方案为i=10--"1010",与其上一个方案y=8--"1000"相比,第二个工作被分配了,第二个工作对应的索引是1,所以此时的x=1
            //输入为{1,2,4,7,8}, 2 时,x的值0 1 0 2 0 1 0 3 0 1 0 2 0 1 0 4 0 1 0 2 0 1 0 3 0 1 0 2 0 1 0

            int y = i-(1<<x);//y得到的是上一个分配方案,如i=2时,y=0;原因是2的二进制为"10",由"00"改变得到的,为什么不是1呢,因为1对于的二进制是"1",只有一位,而2对应的"10"是两位,其余的依此类推
            //输入为{1,2,4,7,8}, 2 时,y的值0 0 2 0 4 4 6 0 8 8 10 8 12 12 14 0 16 16 18 16 20 20 22 16 24 24 26 24 28 28 30

            //sum数组存储的是不同分配方案下,对应的工作时长,方便后面直接取用
            sum[i] = sum[y] + jobs[x];//输入为{1,2,4,7,8}, 2 时,sum数组中的值   0 1 2 3 4 5 6 7 7 8 9 10 11 12 13 14 8 9 10 11 12 13 14 15 15 16 17 18 19 20 21 22
        }

        //因为当对第i个工人分配工作时,分配方案由前i-1个工人的分配方案所决定或者说是限制,所以使用dp算法
        int [][] dp = new int[k][1<<n];//对于dp[i][j],i表示第几个工人,j表示目前分配的方案(因为使用的是状态压缩,所以j的二进制形式表示不同的工作分配方式)

        for(int i=0; i<(1<<n); i++)//使用sum数组对dp[0]进行初始化
            dp[0][i] = sum[i];

        for(int i=1; i<k; i++)//对于k个工人,逐个分配工作
            for(int j=0; j<(1<<n);j++)//对于(1<<n)种分配方案,逐个尝试
            {
                int min = Integer.MAX_VALUE;
                for (int x = j; x != 0; x = (x-1)&j)//对前j个方案依次进行测试,得到每种分配方案的工作时长最小值
                    min = Math.min(min, Math.max(dp[i-1][j-x], sum[x]));

                dp[i][j] = min;
            }

        return dp[k-1][(1<<n)-1];//dp的最后一个值意味着所有的工人已经分配完了全部的工作的方案中的最小值,所以为最终的答案
    }

测试用例–便于理解

当输入为[1,2,4,7,8],k=2时,对应的x和y的值,方便理解

ixy
100
210
302
420
504
614
706
830
908
1018
11010
1228
13012
14112
15014
1640
17016
18116
19018
20216
21020
22120
23022
24316
25024
26124
27026
28224
29028
30128
31030
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文编码格式:首先,我们可以检查所编辑的文的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值