【经典专题】给工人分配工作——回溯优化连环问

题目描述

给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。

请你将这些工作分配给 k 位工人。如何尽快完成这些工作?请给出你的解决方案。

示例:jobs = [1, 2, 4, 7, 8], k = 2
解释:1 号工人工作时间为 1 + 2 + 8 = 11,2 号工人工作时间为 4 + 7 = 11,最终工作完成时间是 11 。

 
 

基本思路

回溯法。其实就是暴力,试探出所有的分配情况,分配完所有工作后(index == jobs.length),尝试更新最终工作完成时间的最小值。

class Solution {
    public int minimumTimeRequired(int[] jobs, int k) {
        traceback(jobs, 0, new int[k]);
        return res;
    }

    private int res = Integer.MAX_VALUE;

    private void traceback(int[] jobs, int index, int[] workers) {
        if (index == jobs.length) {
            res = Math.min(res, Arrays.stream(workers).max().getAsInt());
            return;
        }
        for (int i = 0; i < workers.length; i++) {
            workers[i] += jobs[index];
            traceback(jobs, index + 1, workers);
            workers[i] -= jobs[index];
        }
    }
}

 
 

优化1:减枝,如果某次分配后,某个工人的时长已经超过所维护的最小值,则尽快结束这个递归

很朴素的减枝,但极其有效。

class Solution {
    public int minimumTimeRequired(int[] jobs, int k) {
        traceback(jobs, 0, new int[k]);
        return res;
    }

    private int res = Integer.MAX_VALUE;

    private void traceback(int[] jobs, int index, int[] workers) {
    	// 优化1:如果某次分配后,某个工人的时长已经超过所维护的最小值,则尽快结束这个递归
    	if (Arrays.stream(workers).max().getAsInt() >= res) {
            return;
        }
        if (index == jobs.length) {
            res = Math.min(res, Arrays.stream(workers).max().getAsInt());
            return;
        }
        for (int i = 0; i < workers.length; i++) {
            workers[i] += jobs[index];
            traceback(jobs, index + 1, workers);
            workers[i] -= jobs[index];
        }
    }
}

 
 

优化2:如果第i-1个工人没有被分配工作,那么就不给第i个工人分配工作

比如有ABCD四个工人,B还没分配工作呢,就完全没有必要给C或D分配工作。

这是因为,ABCD本身就是等价的,没有必要根据它们的位置进行不必要的递归。

class Solution {
    public int minimumTimeRequired(int[] jobs, int k) {
        traceback(jobs, 0, new int[k]);
        return res;
    }

    private int res = Integer.MAX_VALUE;

    private void traceback(int[] jobs, int index, int[] workers) {
    	// 优化1:如果某次分配后,某个工人的时长已经超过所维护的最小值,则尽快结束这个递归
    	if (Arrays.stream(workers).max().getAsInt() >= res) {
            return;
        }
        if (index == jobs.length) {
            res = Math.min(res, Arrays.stream(workers).max().getAsInt());
            return;
        }
        for (int i = 0; i < workers.length; i++) {
        	// 优化2:如果第i-1个工人没有被分配工作,那么就不给第i个工人分配工作
            if (i > 0 && workers[i - 1] == 0) {
                continue;
            }
            workers[i] += jobs[index];
            traceback(jobs, index + 1, workers);
            workers[i] -= jobs[index];
        }
    }
}

 
 

优化3:先分配较大的任务,可以尽快知道本方案不可行,即可以尽快减枝

我们感性的理解一下,如果要求将石头和沙子放入玻璃瓶中,优先放入石头更容易使得工作变得简单。

如果你学习过操作系统的内存分配,那么其实也不难理解。

class Solution {
    public int minimumTimeRequired(int[] jobs, int k) {
    	// 优化3:先分配较大的任务,可以尽快知道本方案不可行,即可以尽快减枝
        Integer[] _jobs = Arrays.stream(jobs).boxed().toArray(Integer[]::new);
        Arrays.sort(_jobs, (o1, o2) -> o2 - o1);
        traceback(_jobs, 0, new int[k]);
        return res;
    }

    private int res = Integer.MAX_VALUE;

    private void traceback(Integer[] jobs, int index, int[] workers) {
    	// 优化1:如果某次分配后,某个工人的时长已经超过所维护的最小值,则尽快结束这个递归
    	if (Arrays.stream(workers).max().getAsInt() >= res) {
            return;
        }
        if (index == jobs.length) {
            res = Math.min(res, Arrays.stream(workers).max().getAsInt());
            return;
        }
        for (int i = 0; i < workers.length; i++) {
        	// 优化2:如果第i-1个工人没有被分配工作,那么就不给第i个工人分配工作
            if (i > 0 && workers[i - 1] == 0) {
                continue;
            }
            workers[i] += jobs[index];
            traceback(jobs, index + 1, workers);
            workers[i] -= jobs[index];
        }
    }
}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

E N D END END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值