Maximum Number of Tasks You Can Assign

Hard
#1886Time: 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)`.3 companies

Prompt

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.length
  • m == workers.length
  • 1 <= n, m <= 5 * 104
  • 0 <= pills <= m
  • 0 <= 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

  1. Binary Search on the Answer: The number of tasks we can complete is between 0 and min(n, m). Since if we can complete k tasks, we can also complete k-1 tasks, the problem has a monotonic property. This allows us to binary search for the maximum possible value of k.
  2. Check Feasibility (can_assign(k)): For a given k, we need to determine if it's possible to assign k tasks.
    • To maximize our chances, we should use the k easiest tasks and the k strongest workers. Therefore, we first sort both the tasks and workers arrays.
    • We will try to assign tasks[0...k-1] using workers from workers[m-k...m-1].
  3. Naive Greedy Assignment: We iterate through the k tasks from hardest to easiest (tasks[k-1] down to tasks[0]).
    • For each task, we search for a suitable worker from a list containing the k strongest 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.
  4. 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.
  5. Result: If we can assign all k tasks, can_assign(k) is true, and we try for a larger k. Otherwise, it's false, and we try for a smaller k.

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:

  1. Sort both tasks and workers arrays initially (can be done once outside the binary search).
  2. Create a mutable list (like ArrayList) and populate it with the k strongest workers: workers[m-k], ..., workers[m-1].
  3. Keep a count of the remaining pills.
  4. Iterate from i = k-1 down to 0 (processing tasks from hardest to easiest).
  5. 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 w such that w >= 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 worker w such that w + 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 k tasks. Return false.
  6. If the loop completes, it means all k tasks were assigned. Return true.
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.

Solutions

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;  }}

Video 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.