任務調度器
題目描述
給定一個用字符數組表示的 CPU 需要執行的任務列表。其中包含使用大寫的 A - Z 字母表示的26 種不同種類的任務。任務可以以任意順序執行,並且每個任務都可以在 1 個單位時間內執行完。CPU 在任何一個單位時間內都可以執行一個任務,或者在待命狀態。
然而,兩個相同種類的任務之間必須有長度爲 n 的冷卻時間,因此至少有連續 n 個單位時間內 CPU 在執行不同的任務,或者在待命狀態。
你需要計算完成所有任務所需要的最短時間。
示例 1:
輸入: tasks = ["A","A","A","B","B","B"], n = 2
輸出: 8
執行順序: A -> B -> (待命) -> A -> B -> (待命) -> A -> B.
注:
- 任務的總個數爲 [1, 10000]。
- n 的取值範圍爲 [0, 100]。
解題思路
個人AC
class Solution {
public int leastInterval(char[] tasks, int n) {
int st = 0;
int[] cnts = new int[26];
int total = 0;
for (int i = 0; i < tasks.length; i++) {
cnts[tasks[i] - 'A']++;
total++;
}
// 記錄任務'X'最後一次執行的時間,從1開始
// key的類型爲Integer,A對應0,B對應1,依此類推...
// value的類型爲Integer,對應其最後一次出現的時間
HashMap<Integer, Integer> map = new HashMap<>();
while (total > 0) {
for (int i = 0; total > 0 && i < cnts.length; i++) {
if (cnts[i] == 0) continue;
cnts[i]--;
total--;
if (map.containsKey(i)) {
int interval = st - map.get(i);
if (n - interval > 0) st += n - interval;
}
st += 1;
map.put(i, st);
System.out.println(st);
}
}
return st;
}
}
未通過。
應該在相同任務等待間隔中儘可能地安排其他任務。
最優解
排序
由於相同的任務之間必須有 n 的冷卻時間,所以我們可以想到按照任務的數量來安排它們,即一種任務的出現次數越多,我們就越早地安排。例如有 5 種任務 A, B, C, D, E,且它們分別有 6, 1, 1, 1, 1 個時,假設冷卻時間 n = 2,那麼我們首先安排任務 A,隨後在 2 單位的冷卻時間裏,我們安排任務 B, C,隨後繼續安排任務 A,再安排任務 D, E,以此類推。
因此我們得到了一種安排的方法:我們規定 n + 1 個任務爲一輪,這樣的好處是同一輪中一個任務最多隻能被安排一次。在每一輪中,我們將當前的任務按照它們剩餘的次數降序排序,並選擇剩餘次數最多的 n + 1 個任務依次執行。如果任務的種類 t 少於 n + 1 個,就只選擇全部的 t 種任務,其餘的時間空閒。這樣做的正確性在於,由於冷卻時間的存在,出現次數較多的那些任務如果不盡早安排,將會導致大量空閒時間的出現,因此貪心地將出現次數較多的任務安排在前面是合理的。同時我們可以保證,這一輪的第 k 個任務距離上一次執行至少有 n 個單位的冷卻時間。我們可以使用逆向思維來證明:假設第 r 輪中某個任務在第 k 個執行,那麼說明它在第 r 輪時爲數量第 k 多的任務。在第 r 輪結束後,第 1 多到第 k 多的任務的數量都會減少 1,因此在第 r + 1 輪,這個任務最多也只能是數量第 k 多,因此它如果被執行,一定滿足冷卻時間的要求。
根據上面的安排方法,我們每一輪選擇不超過 n + 1 個任務執行,直到所有的任務被執行。
- 將任務按類型進行分組,統計個數;
- 對得到的分組進行排序;
- 首先排列個數最多的任務T,需要的時間最多爲 T -> X * n -> T -> X * n -> T:
- st = (m - 1) * (n + 1) + 1;
- 統計後續和個數最多的任務T數量相同的任務,T -> X * n -> T -> X * n -> T -> A -> B -> C。
class Solution {
public int leastInterval(char[] tasks, int n) {
if (tasks.length <= 1 || n < 1) return tasks.length;
int st = 0;
// 將任務按類型分組
int[] cnts = new int[26];
for (char task : tasks) {
cnts[task - 'A']++;
}
// 將cnts按從小到大的順序排序,優先排列個數最多的任務
Arrays.sort(cnts);
int i = 25; // 個數最多的任務
// 只排列個數最多的任務T,需要的時間至少爲 T -> X * n -> T -> X * n -> T
st = (cnts[i--] - 1) * (n + 1) + 1;
// 如果後面的任務有和最多的任務數量相同的話,則st + 1
while (i >= 0 && cnts[i] == cnts[25]) {
st++;
i--;
}
return Math.max(st, tasks.length);
}
}
時間複雜度: ,對長度爲26的數組排序;
空間複雜度: 。