给你一个用字符数组 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 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
**输入:** 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]
方法一:模拟
思路与算法
一种容易想到的方法是,我们按照时间顺序,依次给每一个时间单位分配任务。
那么如果当前有多种任务不在冷却中,那么我们应该如何挑选执行的任务呢?直觉上,我们应当选择剩余执行次数最多的那个任务,将每种任务的剩余执行次数尽可能平均,使得 CPU 处于待命状态的时间尽可能少。当然这也是可以证明的,详细证明见下一个小标题。
因此我们可以用二元组 (\textit{nextValid}_i, \textit{rest}_i) 表示第 i 个任务,其中 nextValid}_i 表示其因冷却限制,最早可以执行的时间;rest}_i 表示其剩余执行次数。初始时,所有的 nextValid}_i 均为 1,而 rest}_i 即为任务 i 在数组 tasks 中出现的次数。
我们用 time 记录当前的时间。根据我们的策略,我们需要选择不在冷却中并且剩余执行次数最多的那个任务,也就是说,我们需要找到满足 nextValid}_i \leq \textit{time 的并且 rest}_i 最大的索引 i。因此我们只需要遍历所有的二元组,即可找到 i。在这之后,我们将 (\textit{nextValid}_i, \textit{rest}_i) 更新为 (\textit{time}+n+1, \textit{rest}_i-1),记录任务 i 下一次冷却结束的时间以及剩余执行次数。如果更新后的 rest}_i=0,那么任务 i 全部做完,我们在遍历二元组时也就可以忽略它了。
而对于 time 的更新,我们可以选择将其不断增加 1,模拟每一个时间片。但这会导致我们在 CPU 处于待命状态时,对二元组进行不必要的遍历。为了减少时间复杂度,我们可以在每一次遍历之前,将 time 更新为所有 nextValid}_i 中的最小值,直接「跳过」待命状态,保证每一次对二元组的遍历都是有效的。需要注意的是,只有当这个最小值大于 time 时,才需要这样快速更新。
证明
对于某个时间点 t,设任务 a 和 b 均不在冷却中,并且它们分别剩余 p 和 q 次。不失一般性,假设 p>q,那么我们应当在此时选择任务 a,但我们选择了任务 b。我们需要证明,存在一种交换方法,使得将此时的任务 b「变成」任务 a 后,总时间不会增加。
为了叙述方便,设 a_1, a_2, \cdots, a_p 为选择任务 a 的时间点,b_1, b_2, \cdots, b_q 为选择任务 b 的时间点,根据假设有
classSolution { public: intleastInterval(vector<char>& tasks, int n){ unordered_map<char, int> freq; for (char ch: tasks) { ++freq[ch]; } // 任务总数 int m = freq.size(); vector<int> nextValid, rest; for (auto [_, v]: freq) { nextValid.push_back(1); rest.push_back(v); }
int time = 0; for (int i = 0; i < tasks.size(); ++i) { ++time; int minNextValid = INT_MAX; for (int j = 0; j < m; ++j) { if (rest[j]) { minNextValid = min(minNextValid, nextValid[j]); } } time = max(time, minNextValid); int best = -1; for (int j = 0; j < m; ++j) { if (rest[j] && nextValid[j] <= time) { if (best == -1 || rest[j] > rest[best]) { best = j; } } } nextValid[best] = time + n + 1; --rest[best]; }
# 任务总数 m = len(freq) nextValid = [1] * m rest = list(freq.values())
time = 0 for i inrange(len(tasks)): time += 1 minNextValid = min(nextValid[j] for j inrange(m) if rest[j] > 0) time = max(time, minNextValid) best = -1 for j inrange(m): if rest[j] and nextValid[j] <= time: if best == -1or rest[j] > rest[best]: best = j nextValid[best] = time + n + 1 rest[best] -= 1
let time = 0; for (let i = 0; i < tasks.length; i++) { time++; let minNextValid = Number.MAX_VALUE; for (let j = 0; j < m; j++) { if (rest[j] > 0) { minNextValid = Math.min(nextValid[j], minNextValid); } } time = Math.max(time, minNextValid);
let best = -1; for (let j = 0; j < m; j++) { if (rest[j] && nextValid[j] <= time) { if (best === -1 || rest[j] > rest[best]) { best = j; } } }
funcleastInterval(tasks []byte, n int) (minTime int) { cnt := map[byte]int{} for _, t := range tasks { cnt[t]++ }
nextValid := make([]int, 0, len(cnt)) rest := make([]int, 0, len(cnt)) for _, c := range cnt { nextValid = append(nextValid, 1) rest = append(rest, c) }
forrange tasks { minTime++ minNextValid := math.MaxInt64 for i, r := range rest { if r > 0 && nextValid[i] < minNextValid { minNextValid = nextValid[i] } } if minNextValid > minTime { minTime = minNextValid } best := -1 for i, r := range rest { if r > 0 && nextValid[i] <= minTime && (best == -1 || r > rest[best]) { best = i } } nextValid[best] = minTime + n + 1 rest[best]-- } return }
intleastInterval(char* tasks, int tasksSize, int n) { int freq[26]; memset(freq, 0, sizeof(freq)); for (int i = 0; i < tasksSize; ++i) { ++freq[tasks[i] - 'A']; }
// 任务总数 int m = 0; int nextValid[26], rest[26]; for (int i = 0; i < 26; ++i) { if (freq[i] > 0) { nextValid[m] = 1; rest[m++] = freq[i]; } }
int time = 0; for (int i = 0; i < tasksSize; ++i) { ++time; int minNextValid = INT_MAX; for (int j = 0; j < m; ++j) { if (rest[j]) { minNextValid = fmin(minNextValid, nextValid[j]); } } time = fmax(time, minNextValid); int best = -1; for (int j = 0; j < m; ++j) { if (rest[j] && nextValid[j] <= time) { if (best == -1 || rest[j] > rest[best]) { best = j; } } } nextValid[best] = time + n + 1; --rest[best]; }
intleastInterval(char* tasks, int tasksSize, int n) { int freq[26]; memset(freq, 0, sizeof(freq)); for (int i = 0; i < tasksSize; ++i) { ++freq[tasks[i] - 'A']; }
// 最多的执行次数 int maxExec = 0; for (int i = 0; i < 26; i++) { maxExec = fmax(maxExec, freq[i]); } // 具有最多执行次数的任务数量 int maxCount = 0; for (int i = 0; i < 26; i++) { if (maxExec == freq[i]) { maxCount++; } }