1. 程式人生 > 其它 >LeetCode——任務排程器

LeetCode——任務排程器

1. 任務排程器

這道題一上手會犯直接找模擬方法,然後根據模擬方法來得出結果。也不是說直接找模擬方法不對,只是說一開始沒有更深入的思考的話,模擬方法很可能是錯的,導致浪費時間,像這種題,要注意其中的極限思想,比如這道題,假如其他變數不動,把等待間隔不斷調大會發生什麼?然後出現變化的分界點是什麼?按照這樣思考,就不難出結果了。

題目連結

1.1. 問題

給你一個用字元陣列 tasks 表示的 CPU 需要執行的任務列表。其中每個字母表示一種不同種類的任務。任務可以以任意順序執行,並且每個任務都可以在 1 個單位時間內執行完。在任何一個單位時間,CPU 可以完成一個任務,或者處於待命狀態。然而,兩個相同種類的任務之間必須有長度為整數 n 的冷卻時間,因此至少有連續 n 個單位時間內 CPU 在執行不同的任務,或者在待命狀態。你需要計算完成所有任務所需要的 最短時間 。

比如:
輸入:tasks = ["A","A","A","B","B","B"], n = 2
輸出:8
解釋:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
在本示例中,兩個相同型別任務之間必須間隔長度為 n = 2 的冷卻時間,而執行一個任務只需要一個單位時間,所以中間出現了(待命)狀態。

輸入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
輸出:16
解釋:一種可能的解決方案是:
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

1.2. 思路

這道題我一開始寫錯了。。。
這個是我的錯誤程式碼,對於AAABBBCCCDDE,n = 2,這種情況,我的程式碼中構造的任務排程方式為:ABC|ABC|ABC|DE |D,一共需要13個單位時間。
但實際上12個單位時間就夠了。。。這明顯是我任務排程方式安排的不對。。。

    //錯誤程式碼
    public static int leastInterval(char[] tasks, int n) {
        if (tasks == null) return 0;
        if (tasks.length < 2 || n == 0) return tasks.length;
        int bucketNum = 0;
        int fullNum = 0;
        int[] buckets = new int[tasks.length];
        int bucketSize = n + 1;
        int[] times = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            times[tasks[i] - 'A']++;
        }

        for (int i = 0; i < 26; i++) {
            if (times[i] > 0) {
                int end = fullNum + times[i];
                for (int j = fullNum; j < Math.min(end, bucketNum); j++) {
                    if (++buckets[j] == bucketSize) {
                        fullNum++;
                    }
                }
                for (int j = bucketNum; j < end; j++) {
                    buckets[bucketNum++] = 1;
                }
            }
        }
        return (bucketNum - 1) * bucketSize + buckets[bucketNum - 1];
    }

這道題其實是個極限的思想,關鍵在於相同任務的執行時間間隔:

  1. 如果這個間隔足夠大,那麼這時,最小的執行時間就為(count[25] - 1)× (n+1)+ maxCount
  2. 如果這個間隔不夠大,導致(count[25] - 1)×(n+1)+ maxCount小於taskTotalCount,那麼可以通過某種方式證明,此時的執行時間為taskTotalCount

第二點證明如下:
在第二點的情況下,那麼產生ceil(totalTaskCount / (n + 1))個時間間隔(因為此時(count[25] - 1) × (n + 1) + 1 < taskTotalCount,所以ceil(totalTaskCount / (n + 1))一定不小於count[25],然後將排序好的任務按照迴圈時間段的方式來填:

對於AAABBBCCCDDE,n = 2
---/---/---/---
A--/A--/A--/B--
AB-/AB-/AC-/BC-
ABC/ABD/ACD/BCE

按照這種方式來填的話,只有最後一個時間段才可能填不滿,並且一定滿足除了出現次數最多的任務的其他任務的時間間隔的需求:

  • ceil(totalTaskCount / (n + 1))count[25]相等:由於是次數多的任務先填,次數小的任務後填,所以不可能出現次數和count[25]相等的其他任務第一次填的時間段不是第一個時間段導致時間間隔不滿足要求的情況。

  • ceil(totalTaskCount / (n + 1))大於count[25],那麼很顯然,每種任務的時間間隔要求都能滿足。

    public int leastInterval(char[] tasks, int n) {
                int[] count = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i]-'A']++;
        }//統計詞頻
        Arrays.sort(count);//詞頻排序,升序排序,count[25]是頻率最高的
        int maxCount = 0;
        //統計有多少個頻率最高的字母
        for (int i = 25; i >= 0; i--) {
            if(count[i] != count[25]){
                break;
            }
            maxCount++;
        }
        //公式算出的值可能會比陣列的長度小,取兩者中最大的那個
        return Math.max((count[25] - 1) * (n + 1) + maxCount , tasks.length);

    }