Maximum Number of Tasks You Can Assign
HardPrompt
You have n tasks and m workers. Each task has a strength requirement stored in a 0-indexed integer array tasks, with the ith task requiring tasks[i] strength to complete. The strength of each worker is stored in a 0-indexed integer array workers, with the jth worker having workers[j] strength. Each worker can only be assigned to a single task and must have a strength greater than or equal to the task's strength requirement (i.e., workers[j] >= tasks[i]).
Additionally, you have pills magical pills that will increase a worker's strength by strength. You can decide which workers receive the magical pills, however, you may only give each worker at most one magical pill.
Given the 0-indexed integer arrays tasks and workers and the integers pills and strength, return the maximum number of tasks that can be completed.
Example 1:
Input: tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1
Output: 3
Explanation:
We can assign the magical pill and tasks as follows:
- Give the magical pill to worker 0.
- Assign worker 0 to task 2 (0 + 1 >= 1)
- Assign worker 1 to task 1 (3 >= 2)
- Assign worker 2 to task 0 (3 >= 3)Example 2:
Input: tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5
Output: 1
Explanation:
We can assign the magical pill and tasks as follows:
- Give the magical pill to worker 0.
- Assign worker 0 to task 0 (0 + 5 >= 5)Example 3:
Input: tasks = [10,15,30], workers = [0,10,10,10,10], pills = 3, strength = 10
Output: 2
Explanation:
We can assign the magical pills and tasks as follows:
- Give the magical pill to worker 0 and worker 1.
- Assign worker 0 to task 0 (0 + 10 >= 10)
- Assign worker 1 to task 1 (10 + 10 >= 15)
The last pill is not given because it will not make any worker strong enough for the last task.
Constraints:
n == tasks.lengthm == workers.length1 <= n, m <= 5 * 1040 <= pills <= m0 <= tasks[i], workers[j], strength <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
The core idea is to binary search for the maximum number of tasks, k, that can be assigned. The range for our binary search will be from 0 to min(n, m). For each k we test, we need a helper function, can_assign(k), to check if it's possible to complete k tasks.
To give ourselves the best chance of success for a given k, we should always attempt to complete the k easiest tasks using the k strongest workers. This is a critical greedy insight. So, we begin by sorting both the tasks and workers arrays in ascending order.
In this first approach, the can_assign(k) function is implemented naively. We take the k strongest workers (i.e., workers[m-k] to workers[m-1]) and put them into a list. Then, we iterate through the k easiest tasks from hardest to easiest (tasks[k-1] down to tasks[0]). For each task, we perform a linear scan through our list of available workers to find the weakest one who can complete the task, first without a pill, and if that's not possible, then with a pill. This linear scan for each task leads to a quadratic time complexity for the check function, which is generally too slow.
Algorithm
- Binary Search on the Answer: The number of tasks we can complete is between 0 and
min(n, m). Since if we can completektasks, we can also completek-1tasks, the problem has a monotonic property. This allows us to binary search for the maximum possible value ofk. - Check Feasibility (
can_assign(k)): For a givenk, we need to determine if it's possible to assignktasks.- To maximize our chances, we should use the
keasiest tasks and thekstrongest workers. Therefore, we first sort both thetasksandworkersarrays. - We will try to assign
tasks[0...k-1]using workers fromworkers[m-k...m-1].
- To maximize our chances, we should use the
- Naive Greedy Assignment: We iterate through the
ktasks from hardest to easiest (tasks[k-1]down totasks[0]).- For each task, we search for a suitable worker from a list containing the
kstrongest workers. - We first try to find the weakest worker who can complete the task without a pill (
worker_strength >= task_strength). - If no such worker is found, we try to find the weakest worker who can complete it with a pill (
worker_strength + strength >= task_strength), provided we have pills left. - The search for a worker in the list takes linear time relative to the number of available workers.
- For each task, we search for a suitable worker from a list containing the
- Update State: If a worker is assigned, they are removed from the list of available workers. If a pill is used, the count of pills is decremented.
- Result: If we can assign all
ktasks,can_assign(k)is true, and we try for a largerk. Otherwise, it's false, and we try for a smallerk.
Walkthrough
The overall algorithm is structured as a binary search on the answer. The lower bound is 0 and the upper bound is min(n, m). In each step of the binary search, we check if a given number of tasks, mid, can be completed.
Here's the can_assign(k) function in detail:
- Sort both
tasksandworkersarrays initially (can be done once outside the binary search). - Create a mutable list (like
ArrayList) and populate it with thekstrongest workers:workers[m-k], ..., workers[m-1]. - Keep a count of the remaining pills.
- Iterate from
i = k-1down to0(processing tasks from hardest to easiest). - For each
task = tasks[i], find a suitable worker in the list:- First, search for a worker who can do it without a pill. We iterate through the list to find the index of the weakest worker
wsuch thatw >= task. If multiple such workers exist, we pick the one with the minimum strength to save stronger workers. - If such a worker is found, remove them from the list and continue to the next task.
- If not, and if
pills > 0, search for a worker who can do it with a pill. We find the index of the weakest workerwsuch thatw + strength >= task. Again, we pick the weakest one possible. - If found, remove them from the list, decrement the pill count, and continue.
- If no worker can be found even with a pill, it's impossible to assign
ktasks. Returnfalse.
- First, search for a worker who can do it without a pill. We iterate through the list to find the index of the weakest worker
- If the loop completes, it means all
ktasks were assigned. Returntrue.
class Solution { public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) { Arrays.sort(tasks); Arrays.sort(workers); int low = 0, high = Math.min(tasks.length, workers.length); int ans = 0; while (low <= high) { int k = low + (high - low) / 2; if (k == 0) { low = k + 1; continue; } if (canAssign(k, tasks, workers, pills, strength)) { ans = k; low = k + 1; } else { high = k - 1; } } return ans; } private boolean canAssign(int k, int[] tasks, int[] workers, int pills, int strength) { List<Integer> workerList = new ArrayList<>(); for (int i = workers.length - k; i < workers.length; i++) { workerList.add(workers[i]); } for (int i = k - 1; i >= 0; i--) { int task = tasks[i]; boolean assigned = false; // Try to assign without a pill int bestWorkerIndex = -1; for (int j = 0; j < workerList.size(); j++) { if (workerList.get(j) >= task) { if (bestWorkerIndex == -1 || workerList.get(j) < workerList.get(bestWorkerIndex)) { bestWorkerIndex = j; } } } if (bestWorkerIndex != -1) { workerList.remove(bestWorkerIndex); assigned = true; } else if (pills > 0) { // Try to assign with a pill bestWorkerIndex = -1; for (int j = 0; j < workerList.size(); j++) { if (workerList.get(j) + strength >= task) { if (bestWorkerIndex == -1 || workerList.get(j) < workerList.get(bestWorkerIndex)) { bestWorkerIndex = j; } } } if (bestWorkerIndex != -1) { workerList.remove(bestWorkerIndex); pills--; assigned = true; } } if (!assigned) { return false; } } return true; }}Complexity
Time
O(n log n + m log m + log(min(n, m)) * k^2). Sorting takes `O(n log n + m log m)`. The binary search performs `log(min(n, m))` calls to `can_assign(k)`. `can_assign(k)` takes `O(k^2)` because for each of the `k` tasks, we may scan the list of up to `k` workers. Given the constraints, this is too slow.
Space
O(min(n, m)) to store the list of `k` workers within the `can_assign` function. The maximum value of `k` is `min(n, m)`.
Trade-offs
Pros
The binary search approach correctly narrows down the search space for the answer.
The greedy choice of using the easiest tasks and strongest workers is correct and simplifies the problem.
Cons
The
can_assign(k)function has a time complexity of O(k^2) due to the linear scan for a suitable worker for each of the k tasks.This approach is too slow for the given constraints and will likely result in a 'Time Limit Exceeded' error.
This approach refines the previous one by significantly optimizing the can_assign(k) function. The quadratic complexity of the check was due to the linear scan to find a suitable worker. By replacing the simple list of workers with a more powerful data structure, we can reduce the time for this search.
A multiset is the perfect tool for this job, as it maintains its elements in sorted order and allows for efficient searching (specifically, finding the smallest element greater than or equal to a value) and deletion. In Java, a TreeMap<Integer, Integer> can be used to simulate a multiset, where keys represent worker strengths and values represent the count of workers with that strength.
The greedy logic remains the same: for each task (from hardest to easiest), find the least-qualified worker who can complete it. The TreeMap allows us to perform this search in O(log k) time instead of O(k), dramatically improving the performance of the check function and making the overall solution efficient enough to pass within the time limits.
Algorithm
- Binary Search on the Answer: Same as the previous approach, we binary search for the maximum number of tasks
k. - Optimized Feasibility Check (
can_assign(k)): We again use thekeasiest tasks andkstrongest workers after sorting. - Efficient Greedy Assignment: The key improvement is to use a more efficient data structure to manage the pool of
kworkers. A multiset (simulated using aTreeMapin Java) is ideal. It keeps workers sorted and provides logarithmic time complexity for search (lower_bound) and removal operations. - Assignment Logic: We iterate through tasks from hardest to easiest (
tasks[k-1]down totasks[0]):- For each task
t, we first query theTreeMapfor the weakest worker who can do the job without a pill (w >= t). This corresponds to finding the smallest key in the map that is greater than or equal tot. - If such a worker exists, we assign them, decrement their count in the
TreeMap(or remove the key if the count becomes zero), and move to the next task. - If not, we must use a pill (if available). We query the
TreeMapfor the weakest worker who can do the job with a pill (w >= t - strength). - If this worker is found, we use a pill, update the
TreeMapand pill count, and proceed. - If at any point a required worker cannot be found,
can_assign(k)returnsfalse.
- For each task
- Result: If all
ktasks are successfully assigned,can_assign(k)returnstrue.
Walkthrough
The binary search framework is identical to the previous approach. The improvement lies entirely within the can_assign(k) function.
- Sort
tasksandworkersarrays once. - Inside the
can_assign(k)function: a. Create aTreeMap<Integer, Integer>to act as a multiset. Populate it with thekstrongest workers (workers[m-k...m-1]). The keys will be worker strengths, and values will be their frequencies. b. Initializepills_left = pills. c. Iterate fromi = k-1down to0(hardest to easiest task). d. For the currenttask = tasks[i]: i. Try without a pill: Find the smallest key in theTreeMapthat is>= task. This can be done withtreeMap.ceilingKey(task). ii. If a key is found (i.e., not null), it means we found a worker. We assign this worker. We decrement its count in the map. If the count becomes 0, we remove the key entirely. iii. Try with a pill: If no such worker was found, we check if we have pills left. Ifpills_left > 0, we look for a worker who can do the task with a pill. We need a workerwsuch thatw >= task - strength. We find the smallest key in theTreeMapthat is>= task - strengthusingtreeMap.ceilingKey(task - strength). iv. If this key is found, we assign this worker, use one pill (pills_left--), and update theTreeMapas before. v. Failure: If neither of the above steps results in an assignment, it's impossible to complete this task. We returnfalse. - If the loop finishes, all
ktasks have been assigned, so we returntrue.
import java.util.Arrays;import java.util.TreeMap; class Solution { public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) { Arrays.sort(tasks); Arrays.sort(workers); int low = 0, high = Math.min(tasks.length, workers.length); int ans = 0; while (low <= high) { int k = low + (high - low) / 2; if (k == 0) { low = k + 1; continue; } if (canAssign(k, tasks, workers, pills, strength)) { ans = k; low = k + 1; } else { high = k - 1; } } return ans; } private boolean canAssign(int k, int[] tasks, int[] workers, int pills, int strength) { TreeMap<Integer, Integer> workerCounts = new TreeMap<>(); for (int i = workers.length - k; i < workers.length; i++) { workerCounts.put(workers[i], workerCounts.getOrDefault(workers[i], 0) + 1); } for (int i = k - 1; i >= 0; i--) { int task = tasks[i]; boolean assigned = false; // Try to assign without a pill Integer workerKey = workerCounts.ceilingKey(task); if (workerKey != null) { // Found a worker who can do it without a pill removeWorker(workerCounts, workerKey); assigned = true; } else if (pills > 0) { // Try to assign with a pill workerKey = workerCounts.ceilingKey(task - strength); if (workerKey != null) { // Found a worker who can do it with a pill pills--; removeWorker(workerCounts, workerKey); assigned = true; } } if (!assigned) { return false; } } return true; } private void removeWorker(TreeMap<Integer, Integer> map, int key) { map.put(key, map.get(key) - 1); if (map.get(key) == 0) { map.remove(key); } }}Complexity
Time
O(n log n + m log m + log(min(n, m)) * k * log k). Sorting is `O(n log n + m log m)`. The binary search calls `can_assign` `log(min(n, m))` times. `can_assign(k)` populates a `TreeMap` with `k` elements (`O(k log k)`) and then iterates `k` times, with each iteration performing `O(log k)` operations. The total complexity is well within limits.
Space
O(min(n, m)) to store the `k` workers in the `TreeMap`. In the worst case, `k` is `min(n, m)` and all worker strengths are unique.
Trade-offs
Pros
Highly efficient, with a time complexity that should comfortably pass the given constraints.
The logic is robust and correctly models the optimal assignment strategy.
Cons
The implementation is more complex due to the use of a
TreeMapto simulate a multiset.The constant factors for
TreeMapoperations might be higher than for simple array operations, though the overall asymptotic complexity is much better.
Solutions
Solution
class Solution {private int[] tasks;private int[] workers;private int strength;private int pills;private int m;private int n;public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) { Arrays.sort(tasks); Arrays.sort(workers); this.tasks = tasks; this.workers = workers; this.strength = strength; this.pills = pills; n = tasks.length; m = workers.length; int left = 0, right = Math.min(m, n); while (left < right) { int mid = (left + right + 1) >> 1; if (check(mid)) { left = mid; } else { right = mid - 1; } } return left; }private boolean check(int x) { int i = 0; Deque<Integer> q = new ArrayDeque<>(); int p = pills; for (int j = m - x; j < m; ++j) { while (i < x && tasks[i] <= workers[j] + strength) { q.offer(tasks[i++]); } if (q.isEmpty()) { return false; } if (q.peekFirst() <= workers[j]) { q.pollFirst(); } else if (p == 0) { return false; } else { --p; q.pollLast(); } } return true; }}class Solution {public: int maxTaskAssign(vector<int> &tasks, vector<int> &workers, int pills, int strength) { sort(tasks.begin(), tasks.end()); sort(workers.begin(), workers.end()); int n = tasks.size(), m = workers.size(); int left = 0, right = min(m, n); auto check = [&](int x) { int p = pills; deque<int> q; int i = 0; for (int j = m - x; j < m; ++j) { while (i < x && tasks[i] <= workers[j] + strength) { q.push_back(tasks[i++]); } if (q.empty()) { return false; } if (q.front() <= workers[j]) { q.pop_front(); } else if (p == 0) { return false; } else { --p; q.pop_back(); } } return true; }; while (left < right) { int mid = (left + right + 1) >> 1; if (check(mid)) { left = mid; } else { right = mid - 1; } } return left; }};class Solution: def maxTaskAssign(self, tasks: List[int], workers: List[int], pills: int, strength: int) -> int: def check(x): i = 0 q = deque() p = pills for j in range(m - x, m): while i < x and tasks[i] <= workers[j] + strength: q . append(tasks[i]) i += 1 if not q: return False if q[0] <= workers[j]: q . popleft() elif p == 0: return False else: p -= 1 q . pop() return True n, m = len(tasks), len(workers) tasks . sort() workers . sort() left, right = 0, min(n, m) while left < right: mid = (left + right + 1) >> 1 if check(mid): left = mid else: right = mid - 1 return leftVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.