题目描述
给你一个整数数组 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