[LeetCode] 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.

Example 1:

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

 

Note:

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

 

這道題讓我們安排CPU的任務,規定在兩個相同任務之間至少隔n個時間點。說實話,剛開始博主並沒有完全理解題目的意思,后來看了大神們的解法才悟出個道理來。下面這種解法參考了大神fatalme的帖子,由於題目中規定了兩個相同任務之間至少隔n個時間點,那么我們首先應該處理的出現次數最多的那個任務,先確定好這些高頻任務,然后再來安排那些低頻任務。如果任務F的出現頻率最高,為k次,那么我們用n個空位將每兩個F分隔開,然后我們按順序加入其他低頻的任務,來看一個例子:

AAAABBBEEFFGG 3

我們發現任務A出現了4次,頻率最高,於是我們在每個A中間加入三個空位,如下:

A---A---A---A

AB--AB--AB--A   (加入B)

ABE-ABE-AB--A   (加入E)

ABEFABE-ABF-A   (加入F,每次盡可能填滿或者是均勻填充)

ABEFABEGABFGA   (加入G)

再來看一個例子:

ACCCEEE 2

我們發現任務C和E都出現了三次,那么我們就將CE看作一個整體,在中間加入一個位置即可:

CE-CE-CE

CEACE-CE   (加入A)

注意最后面那個idle不能省略,不然就不滿足相同兩個任務之間要隔2個時間點了。

這道題好在沒有讓我們輸出任務安排結果,而只是問所需的時間總長,那么我們就想個方法來快速計算出所需時間總長即可。我們仔細觀察上面兩個例子可以發現,都分成了(mx - 1)塊,再加上最后面的字母,其中mx為最大出現次數。比如例子1中,A出現了4次,所以有A---模塊出現了3次,再加上最后的A,每個模塊的長度為4。例子2中,CE-出現了2次,再加上最后的CE,每個模塊長度為3。我們可以發現,模塊的次數為任務最大次數減1,模塊的長度為n+1,最后加上的字母個數為出現次數最多的任務,可能有多個並列。這樣三個部分都搞清楚了,寫起來就不難了,我們統計每個大寫字母出現的次數,然后排序,這樣出現次數最多的字母就到了末尾,然后我們向前遍歷,找出出現次數一樣多的任務個數,就可以迅速求出總時間長了,下面這段代碼可能最不好理解的可能就是最后一句了,那么我們特別來講解一下。先看括號中的第二部分,前面分析說了mx是出現的最大次數,mx-1是可以分為的塊數,n+1是每塊中的個數,而后面的 25-i 是還需要補全的個數,用之前的例子來說明:

AAAABBBEEFFGG 3

A出現了4次,最多,mx=4,那么可以分為mx-1=3塊,如下:

A---A---A---

每塊有n+1=4個,最后還要加上末尾的一個A,也就是25-24=1個任務,最終結果為13:

ABEFABEGABFGA

再來看另一個例子:

ACCCEEE 2

C和E都出現了3次,最多,mx=3,那么可以分為mx-1=2塊,如下:

CE-CE-

每塊有n+1=3個,最后還要加上末尾的一個CE,也就是25-23=2個任務,最終結果為8:

CEACE-CE

好,那么此時你可能會有疑問,為啥還要跟原任務個數len相比,取較大值呢?我們再來看一個例子:

AAABBB 0

A和B都出現了3次,最多,mx=3,那么可以分為mx-1=2塊,如下:

ABAB

每塊有n+1=1個?你會發現有問題,這里明明每塊有兩個啊,為啥這里算出來n+1=1呢,因為給的n=0,這有沒有矛盾呢,沒有!因為n表示相同的任務間需要間隔的個數,那么既然這里為0了,說明相同的任務可以放在一起,這里就沒有任何限制了,我們只需要執行完所有的任務就可以了,所以我們最終的返回結果一定不能小於任務的總個數len的,這就是要對比取較大值的原因了。

參見代碼如下:

 

解法一:

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        vector<int> cnt(26, 0);
        for (char task : tasks) {
            ++cnt[task - 'A'];
        }
        sort(cnt.begin(), cnt.end());
        int i = 25, mx = cnt[25], len = tasks.size();
        while (i >= 0 && cnt[i] == mx) --i;
        return max(len, (mx - 1) * (n + 1) + 25 - i);
    }
};

 

下面這種解法是根據大神jinzhou的帖子,優點是代碼更容易讀懂,而且變量命名很reasonable,前半部分都是一樣的,求出最多的次數mx,還有同時出現mx次的不同任務的個數mxCnt。這個解法的思想是先算出所有空出來的位置,然后計算出所有需要填入的task的個數,如果超出了空位的個數,就需要最后再補上相應的個數。注意這里如果有多個任務出現次數相同,那么將其整體放一起,就像上面的第二個例子中的CE一樣,那么此時每個part中的空位個數就是n - (mxCnt - 1),那么空位的總數就是part的總數乘以每個part中空位的個數了,那么我們此時除去已經放入part中的,還剩下的task的個數就是task的總個數減去mx * mxCnt,然后此時和之前求出的空位數相比較,如果空位數要大於剩余的task數,那么則說明還需補充多余的空位,否則就直接返回task的總數即可,參見代碼如下:

 

解法二:

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        int mx = 0, mxCnt = 0;
        vector<int> cnt(26, 0);        
        for (char task : tasks) {
            ++cnt[task - 'A'];
            if (mx == cnt[task - 'A']) {
                ++mxCnt;
            } else if (mx < cnt[task - 'A']) {
                mx = cnt[task - 'A'];
                mxCnt = 1;
            }
        }
        int partCnt = mx - 1;
        int partLen = n - (mxCnt - 1);
        int emptySlots = partCnt * partLen;
        int taskLeft = tasks.size() - mx * mxCnt;
        int idles = max(0, emptySlots - taskLeft);
        return tasks.size() + idles;
    }
};

 

下面這種解法是參考的大神alexander的解法,思路是建立一個優先隊列,然后把統計好的個數都存入優先隊列中,那么大的次數會在隊列的前面。這題還是要分塊,每塊能裝n+1個任務,裝任務是從優先隊列中取,每個任務取一個,裝到一個臨時數組中,然后遍歷取出的任務,對於每個任務,將其哈希表映射的次數減1,如果減1后,次數仍大於0,則將此任務次數再次排入隊列中,遍歷完后如果隊列不為空,說明該塊全部被填滿,則結果加上n+1。我們之前在隊列中取任務是用個變量cnt來記錄取出任務的個數,我們想取出n+1個,如果隊列中任務數少於n+1個,那就用cnt來記錄真實取出的個數,當隊列為空時,就加上cnt的個數,參見代碼如下:

 

解法三:

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        int res = 0, cycle = n + 1;
        unordered_map<char, int> m;
        priority_queue<int> q;
        for (char c : tasks) ++m[c];
        for (auto a : m) q.push(a.second);
        while (!q.empty()) {
            int cnt = 0;
            vector<int> t;
            for (int i = 0; i < cycle; ++i) {
                if (!q.empty()) {
                    t.push_back(q.top()); q.pop();
                    ++cnt;
                }
            }
            for (int d : t) {
                if (--d > 0) q.push(d);
            }
            res += q.empty() ? cnt : cycle;
        }
        return res;
    }
};

 

類似題目:

Rearrange String k Distance Apart

Reorganize String

 

參考資料:

https://leetcode.com/problems/task-scheduler/

https://leetcode.com/problems/task-scheduler/discuss/104493/c-java-clean-code-priority-queue

https://leetcode.com/problems/task-scheduler/discuss/104496/concise-java-solution-on-time-o26-space

https://leetcode.com/problems/task-scheduler/discuss/104500/java-on-time-o1-space-1-pass-no-sorting-solution-with-detailed-explanation

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM