You are given two integers n
(the number of workers) and an array jobs
where jobs[i]
is the time required to complete the i-th job.
Assign all jobs to the n
workers such that each job is assigned to exactly one worker, and each worker can be assigned any number of jobs (including zero).
The working time of a worker is the sum of times of jobs assigned to them.
Your goal is to minimize the maximum working time among all workers after all jobs are assigned.
Return the minimum possible value of the maximum working time among the workers.
Constraints:
The problem is a classic example of job scheduling where we want to distribute jobs among workers to balance their workloads as evenly as possible. If we try every possible assignment, the number of combinations grows exponentially, making brute force impractical for larger inputs.
The key is to minimize the maximum workload among all workers. This is similar to the "minimum makespan" problem in scheduling. We need to find the best way to distribute jobs so that no single worker is overloaded more than necessary.
At first glance, one might consider distributing jobs in order, or assigning the largest job to the worker with the least current load. However, because of the small input size (n, jobs.length ≤ 12), we can use more advanced techniques like backtracking with pruning or binary search over the answer.
We can use a binary search combined with backtracking to efficiently find the minimum possible maximum working time.
This approach efficiently narrows down the minimum possible maximum time using binary search, and uses recursive backtracking to check if a valid assignment exists for each candidate time.
Sample Input:
n = 3
, jobs = [3, 2, 3]
Step 1: The largest job is 3, and the sum is 8. So search between 3 and 8.
Step 2: Try mid = 5
(binary search midpoint).
Step 3: Backtracking to assign jobs:
mid = 4
.
mid = 3
.
mid = 2
. Can't assign 3 to any worker without exceeding 2. So the answer is 3.
Brute-force:
Overall, the solution is efficient for the given constraints.
We tackled the "Find Minimum Time to Finish All Jobs" problem by combining binary search and backtracking. The key insight is to search for the smallest possible maximum working time, and for each candidate, check if a valid assignment exists using recursive backtracking. Sorting jobs in descending order and pruning redundant states makes the solution efficient. This approach elegantly balances brute-force thoroughness with practical optimization, making it both correct and performant for the problem's constraints.
class Solution:
def minimumTimeRequired(self, jobs, k):
jobs.sort(reverse=True)
n = k
res = sum(jobs)
def can_assign(limit):
workloads = [0] * n
def backtrack(i):
if i == len(jobs):
return True
visited = set()
for j in range(n):
if workloads[j] + jobs[i] > limit or workloads[j] in visited:
continue
visited.add(workloads[j])
workloads[j] += jobs[i]
if backtrack(i+1):
return True
workloads[j] -= jobs[i]
if workloads[j] == 0:
break
return False
return backtrack(0)
left, right = max(jobs), sum(jobs)
while left < right:
mid = (left + right) // 2
if can_assign(mid):
right = mid
else:
left = mid + 1
return left
class Solution {
public:
bool canAssign(vector<int>& jobs, vector<int>& workloads, int idx, int limit) {
if (idx == jobs.size()) return true;
unordered_set<int> visited;
for (int i = 0; i < workloads.size(); ++i) {
if (workloads[i] + jobs[idx] > limit || visited.count(workloads[i])) continue;
visited.insert(workloads[i]);
workloads[i] += jobs[idx];
if (canAssign(jobs, workloads, idx+1, limit)) return true;
workloads[i] -= jobs[idx];
if (workloads[i] == 0) break;
}
return false;
}
int minimumTimeRequired(vector<int>& jobs, int k) {
sort(jobs.rbegin(), jobs.rend());
int left = *max_element(jobs.begin(), jobs.end());
int right = accumulate(jobs.begin(), jobs.end(), 0);
while (left < right) {
int mid = left + (right - left) / 2;
vector<int> workloads(k, 0);
if (canAssign(jobs, workloads, 0, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};
class Solution {
public int minimumTimeRequired(int[] jobs, int k) {
Arrays.sort(jobs);
int n = jobs.length;
int left = jobs[n-1], right = 0;
for (int j : jobs) right += j;
int[] workloads = new int[k];
Arrays.sort(jobs);
for (int l = 0, r = n-1; l < r; l++, r--) {
int tmp = jobs[l]; jobs[l] = jobs[r]; jobs[r] = tmp;
}
while (left < right) {
int mid = (left + right) / 2;
Arrays.fill(workloads, 0);
if (canAssign(jobs, workloads, 0, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
private boolean canAssign(int[] jobs, int[] workloads, int idx, int limit) {
if (idx == jobs.length) return true;
HashSet<Integer> visited = new HashSet<>();
for (int i = 0; i < workloads.length; ++i) {
if (workloads[i] + jobs[idx] > limit || visited.contains(workloads[i])) continue;
visited.add(workloads[i]);
workloads[i] += jobs[idx];
if (canAssign(jobs, workloads, idx+1, limit)) return true;
workloads[i] -= jobs[idx];
if (workloads[i] == 0) break;
}
return false;
}
}
var minimumTimeRequired = function(jobs, k) {
jobs.sort((a, b) => b - a);
let left = Math.max(...jobs), right = jobs.reduce((a,b) => a+b, 0);
function canAssign(limit) {
let workloads = new Array(k).fill(0);
function backtrack(i) {
if (i === jobs.length) return true;
let visited = new Set();
for (let j = 0; j < k; ++j) {
if (workloads[j] + jobs[i] > limit || visited.has(workloads[j])) continue;
visited.add(workloads[j]);
workloads[j] += jobs[i];
if (backtrack(i+1)) return true;
workloads[j] -= jobs[i];
if (workloads[j] === 0) break;
}
return false;
}
return backtrack(0);
}
while (left < right) {
let mid = Math.floor((left+right)/2);
if (canAssign(mid)) {
right = mid;
} else {
left = mid+1;
}
}
return left;
};