[LeetCode 621] Task Scheduler

Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks. Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle.

However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle.

You need to return the least number of intervals the CPU will take to finish all the given tasks.

 

 

Constraints:

 

  • The number of tasks is in the range [1, 10000].
  • The integer n is in the range [0, 100].

 

 

Example:

Input: tasks = ["A","A","A","B","B","B"], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.


Solution 1. Simulate using PriorityQueue

Because we need to have interval n in between two same tasks, we know that for a given n + 1 consecutive task arrangment, we can not have duplicated tasks. We can try to come up with a solution using this observation. The only remaining question now is in which order should we pick different tasks to fill in this n + 1 slots? We claim that picking the most frequent task yields the best answer. Say we fill in this n + 1 slots by using a different order with the greedy one, then we can always swap the orders to the greedy order without getting a worse answer. In fact, in cases where we have a dominant most frequent task(its frequency is much higher than the rest tasks), the greedy order will generate a better answer. Why? Because to meet the n gap requirement, we need to fill in some idle intervals if we only have the most frequent tasks to choose from. Always picking greedily gurantees that we will not have idle intervals at the last n + 1 arrangement, and all the previous arrangements use at most the same amout of time with any non-greedy orders.

 

Based on the above analysis, we have the following algorithm.

1. get the frequencies of all tasks and add them to a max pq. 

2. as long as we still have tasks to process, do the following.

(1).  init an time window of length n + 1, representing that we need to fill in n + 1 different tasks if possible

(2). fill in the n + 1 slots one by one greedily, updating each task's remaining frequency, the remaining empty slots, the current time required. Then if the picked tasks are not completely processed,  add it to a list.

(3). check if the list is empty, if it is, it means that we've already processed all tasks, there is no need to add any idle intervals at the end. Otherwise, we need to add the remaining empty slots to the final answer. This is the required empty slots we must have in order to meet the n gap condition.

(4). add all tasks that have not been completely processed back to max pq.

 

For each task, we add and remove it to/from the max pq once, if we have N total tasks to process, the runtime is O(N * logN), space is O(26) as we'll have at most 26 elements in max pq.

 

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] cnt = new int[26];
        for(char c : tasks) {
            cnt[c - 'A']++;
        }
        PriorityQueue<Integer> maxPq = new PriorityQueue<>((a, b) -> {
            if(cnt[a] != cnt[b]) {
                return cnt[b] - cnt[a];
            }
            return a - b;
        });
        for(int i = 0; i < cnt.length; i++) {
            if(cnt[i] > 0) {
                maxPq.add(i);
            }            
        }
        int time = 0;
        while(maxPq.size() > 0) {
            int len = n + 1;
            List<Integer> list = new ArrayList<>();
            while(maxPq.size() > 0 && len > 0) {
                int d = maxPq.poll();
                cnt[d]--;
                len--;
                time++;
                if(cnt[d] > 0) {
                    list.add(d);
                }
            }
            time += (list.size() > 0 ? len : 0);
            maxPq.addAll(list);
        }
        return time;
    }
}

 

 

Solution 2.  Count the least number of idle intervals needed

 

From solution 1's analysis, we know that greedily pick the most frequent tasks in a n + 1 consecutive slots yield an optimal answer. In solution 1, we simulate the arrangement process. Instead, we can try to find the least number of idle intervals needed, then return the sum of all tasks and this idle time count as the final answer. 

 

For simplicity, let's assume we only have one most frequent task, call it A. Then we can lay out all A with a gap of n in between. We then try to fill in these gaps.

We have n * (cnt(A) - 1) empty slots in total.  emptySlots = n * (cnt(A) - 1); We also have remaingTasks = tasks.length - cnt(A) remaining tasks to arrange. There will be 2 cases.

(1) emptySlots > remaingTasks, it means we do not have enough remaining tasks to fill in all the gaps and we must fill in emptySlots - remainingTasks idle intervals. In this case, we are gold, the final answer is tasks.length + emptySlots - remainingTasks. 

(2)  emptySlots <= remaingTasks, the minimum gaps between As are not big enough to fill in all remaining tasks. But because the requirement is the gap is at least n, we can freely extend this gap as needed. In this case, we can extend the gaps to the right length such that all remaining tasks are filled and no idle time is needed.

 

Because we first arrange A, so its gaps already meet the requirement. Arranging remaining tasks in the same order in each new n + 1 slots will ensure that for each task, it has a minimum gap of n.

 

What about if we have more than one most frequent task? In this case, we can treat all these most frequent tasks as one combined task. 

emptySlots = (n - (number of most frequent tasks - 1)) * (cnt(most frequent) - 1); 

remainingTasks = tasks.length - cnt(most frequent) * number of most frequent tasks; 

 

Then we can apply the same analysis of the above simple case.

 

 

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] cnt = new int[26];
        int maxCntNum = 0, maxCnt = 0;
        for(char c : tasks) {
            cnt[c - 'A']++;
        }
        for(int i = 0; i < cnt.length; i++) {
            if(cnt[i] > maxCnt) {
                maxCnt = cnt[i];
                maxCntNum = 1;
            }
            else if(cnt[i] == maxCnt) {
                maxCntNum++;
            }
        }

        int partCnt = maxCnt - 1;
        int emptySlots = partCnt * (n - maxCntNum + 1);
        int remainingTasks = tasks.length - maxCnt * maxCntNum;
        int idles = Math.max(0, emptySlots - remainingTasks);
        return tasks.length + idles;
    }
}

 

posted @ 2020-05-27 03:13  Review->Improve  阅读(244)  评论(0编辑  收藏  举报