【LeetCode】每日一題621. 任務排程器

喜歡下雨所以愛上雷震子發表於2020-12-05

621. 任務排程器

給你一個用字元陣列 tasks 表示的 CPU 需要執行的任務列表。其中每個字母表示一種不同種類的任務。任務可以以任意順序執行,並且每個任務都可以在 1 個單位時間內執行完。在任何一個單位時間,CPU 可以完成一個任務,或者處於待命狀態。

然而,兩個 相同種類 的任務之間必須有長度為整數 n 的冷卻時間,因此至少有連續 n 個單位時間內 CPU 在執行不同的任務,或者在待命狀態。

你需要計算完成所有任務所需要的 最短時間 。

示例 1:

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

示例 2:

輸入:tasks = ["A","A","A","B","B","B"], n = 0
輸出:6
解釋:在這種情況下,任何大小為 6 的排列都可以滿足要求,因為 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
諸如此類

示例 3:

輸入: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 <= task.length <= 104
  • tasks[i] 是大寫英文字母
  • n 的取值範圍為 [0, 100]

方法一:優先佇列

因為要最短時間,在間隔允許的情況下,應該選擇任務次數剩餘最多的執行,所以採用優先佇列把任務次數多的放前面。

  • 先統計每個任務的次數,再根據出現次數從大到小構建優先佇列
  • 因為每個任務需要間隔 n ,所以每輪需要從佇列中彈出 n + 1 個任務執行,不夠 n + 1 剩餘的執行「待命」
  • 彈出來的任務執行後還有次數,放入臨時表 temp 中,在一輪結束後放回佇列
public int leastInterval(char[] tasks, int n) {
    // 統計每個任務的次數
    int[] counts  = new int[26];
    for (Character c : tasks) {
        counts[c - 'A']++;
    }

    // 根據任務次數從大到小構建優先佇列
    PriorityQueue<Character> queue = new PriorityQueue<>(Comparator.comparingInt(c -> -counts[c - 'A']));
    for (int i = 0; i < 26; i++) {
        if (counts[i] > 0) {
            queue.offer((char) (i + 'A'));
        }
    }

    int time = 0;
    List<Character> temp;// 臨時儲存每輪從佇列中取出來的任務
    while (queue.size() > 0) {
        temp = new ArrayList<>();
        // 每輪執行 n + 1 個任務
        for (int i = 0; i < n + 1; i++) {
            if (queue.size() > 0) {
                time++;
                char c = queue.poll();
                // 還有次數放入temp,結束的時候再一起放回佇列
                if (--counts[c - 'A'] > 0) {
                    temp.add(c);
                }
            } else {
                // temp為空,說明是最後一輪,最後一輪不需要(待命)
                if (temp.size() > 0) {
                    time++;
                }
            }
        }
        queue.addAll(temp);
    }
    return time;
}

方法二:構造

因為同類任務最少間隔 n,一輪任務就是 n + 1 的時間。設最多的任務需要執行 m 次,不考慮最後一輪的情況,前面一共需要 ( m - 1) * (n + 1) 的時間。下面分析最後一輪可能的情況

  1. 最後一輪還剩下的任務,任務次數必然只有一次
  2. 前面的 m - 1 輪中出現過「待命」:說明最後一輪的任務只剩下總次數為 m 的任務(總次數為 m 的任務可能不止一個),假設總次數為 m 的任務為 k 個。總的時間即為 ( m - 1) * (n + 1) + k
  3. 前面的 m - 1 輪中沒有出現過「待命」:因為最後一輪必然不會出現「待命」,總的時間就是 tasks.length 唄。

綜上:總的時間為 max(( m - 1) * (n + 1) + k, tasks.length)。

public int leastInterval(char[] tasks, int n) {
    if (n == 0) {
        return tasks.length;
    }
    // 統計每個任務的次數
    int[] counts = new int[26];
    for (char ch : tasks) {
        counts[ch - 'A']++;
    }
    // maxExec 最多的任務的次數
    // maxCount 有maxExec次的任務有多少個
    int maxExec = 0, maxCount = 0;
    for (int i = 0; i < 26; i++) {
        if (counts[i] > maxExec) {
            maxExec = counts[i];
            maxCount = 1;
        } else if (counts[i] == maxExec) {
            maxCount++;
        }
    }

    return Math.max((maxExec - 1) * (n + 1) + maxCount, tasks.length);
}

執行結果

在這裡插入圖片描述

相關文章