1. 程式人生 > >LeetCode 621. Task Scheduler (任務排程)

LeetCode 621. Task Scheduler (任務排程)

原題

  1. 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:

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

Reference Answer

思路分析

這道題讓我們安排CPU的任務,規定在兩個相同任務之間至少隔n個時間點。
由於題目中規定了兩個相同任務之間至少隔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,即 3 * 4 + 1,最終結果為13:

ABEFABEGABFGA

再來看另一個例子:

ACCCEEE 2

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

CE-CE-

每塊有n+1=3個,最後還要加上末尾的一個CE, 2 * 3 + 2 = 8,最終結果為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的,這就是要對比取較大值的原因了。

以為是DP做,實則是找規律,這種題。。比較。。考驗建模能力。。對程式碼的熟練性倒是其次了

Code

class Solution(object):
    def leastInterval(self, tasks, n):
        """
        :type tasks: List[str]
        :type n: int
        :rtype: int
        """
        count = collections.Counter(tasks)
        most = count.most_common()[0][1]
        num_most = len([i for i, v in count.items() if v == most])
        time = (most - 1) * (n + 1) + num_most
        return max(time, len(tasks))

程式碼簡單解釋一下:

  • count.most_common()返回類似[('a', 5), ('r', 2), ('b', 2), ('c', 1)],故使用 count.most_common()[0][1] 表示最大次數;
  • num_most = len([i for i, v in count.items() if v == most]) 同理,其中 count.items()為類似('a', 5)格式返回,num_most即是取出值最多的對應字元;

Note

  • 規律不是很好找,這種題。。比較費時
  • Python版本程式碼很巧妙,其中利用most = count.most_common()[0][1]獲得出現次數最多的次數值,方法很巧妙;然後利用列表推導len([i for i, v in count.items() if v == most])更是簡單方便地得到了次數最多對應的字元長度!

參考文獻

[1] https://blog.csdn.net/fuxuemingzhu/article/details/81947087
[2] http://www.cnblogs.com/grandyang/p/7098764.html