任务调度器
- https://leetcode.cn/problems/task-scheduler/
描述
-
给你一个用字符数组 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","C","A","B","D","B"], n = 1
输出:6
解释:一种可能的序列是:A -> B -> C -> D -> A -> B。
由于冷却间隔为 1,你可以在完成另一个任务后重复执行这个任务。
示例 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 <= 10410^4104
- tasks[i] 是大写英文字母
- n 的取值范围为 [0, 100]
Typescript 版算法实现
1 )方案 1
function leastInterval(tasks: string[], n: number): number {
let result = '' // 最终队列执行的结果
const dict = {} // 对归类进行存储
tasks.forEach((item) => {
dict[item] ? (dict[item] ++) : (dict[item] = 1)
})
while(true) {
// 任务清单
const keys = Object.keys(dict)
// 任务处理完毕跳出
if (!keys.length) {
break
}
// 正常处理过程
let tmp = [] // 用于存储 1 + n个任务单元
for (let i = 0; i <= n; i++) {
let max:number = 0 // 找到最大的任务
let key:string
let pos:number
// 遍历任务清单
keys.forEach((item, idx) => {
// 当前任务数量大于max
if (dict[item] > max) {
max = dict[item] // 存储更新max
key = item // 存储更新当前任务
pos = idx // 存储更新当前任务下标索引,用于后续的删除操作
}
})
// 没有匹配到key, 直接跳出此次循环
if (!key) {
break
}
// 找到了key
tmp.push(key) // 临时队列添加当前的key
keys.splice(pos, 1) // 将当前key从任务队列中删除
dict[key] -- // 更新key的长度,已消费完成,删除来更新剩余个数
// 当全部消费完成,移除当前的任务
if (dict[key] < 1) {
delete dict[key]
}
}
result += tmp.join('').padEnd(n + 1, '-') // 如果不全,补充冷却时间
}
// 边界的处理, 最后不要出现冷却时间
result = result.replace(/-+$/, '')
return result.length
}
- 关键需求分析:
- 两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间
- 因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
- 要求,完成所有任务的最短时间
- 从上面的描述和示例中可见
- 队列中有A,B,C,…, 现在开启了一个任务
- 如果当前开启了A任务,那接下来n个的任务中不能有A了
- 如果其他任务不够n的长度,那么要冷却等待
- 只要现在队列中还有任务,我就要处理任务本身和n个任务冷却时间的 n+1 的任务,
- 也就是从队列中取出这些任务来存放,求最短的存放时间
- 如何做到最短,考虑使用最多的任务优先处理,尽量不会有剩余,交叉着来
- 少的来进行插缝作业,这样即保证少的任务使用了
- 又保证多的任务不会用冷却时间处理来占用更多资源
- 这个算法,在实现上效率不高
2 )方案 2
function leastInterval(tasks: string[], n: number): number {
// 初始化一个矩阵,用于存储给定任务的数量的字典
const cnts = Array(26).fill(0);
for(const c of tasks) {
cnts[c.charCodeAt(0) - 'A'.charCodeAt(0)]++;
}
// 统计出任务中对多的数量
let max = 0, tot = 0;
for (let i = 0; i < 26; i++) {
max = Math.max(max, cnts[i]);
}
// 更新tot变量的值,用于统计具有最多执行次数的任务数量
for (let i = 0; i < 26; i++) {
tot += max === cnts[i] ? 1 : 0;
}
// 这里是最核心的算法
return Math.max(tasks.length, (n + 1) * (max - 1) + tot)
};
- 这是官方示例,效率很高
- 这里用到charCodeAt() 方法可返回指定位置的字符的 Unicode 编码
- 后续有时间再研究下:https://leetcode.cn/problems/task-scheduler/solution/ren-wu-diao-du-qi-by-leetcode-solution-ur9w/ 中的方法二:构造
Python3 版算法实现
1 )方案 1
import re
class Solution:
def leastInterval(self, tasks: List[str], n: int) -> int:
result = '' # 最终队列执行的结果
dict = {} # 对归类进行存储
# 对列表进行归类,存储在字典里
for task in tasks:
if task in dict:
dict[task] += 1
else:
dict[task] = 1
while True:
# 任务清单
keys = list(dict.keys())
if(len(keys) == 0): break
tmp = [] # 用于存储 1 + n个任务单元
# 注意这里是 n + 1
for _ in range(n + 1):
max = 0
key = None
pos = 0
# 遍历任务清单
for idx, item in enumerate(keys):
# 当前任务清单
if (dict[item] > max):
max = dict[item] # 存储更新当前max
key = item # 更新存储当前任务
pos = idx # 存储更新当前任务下标索引,用于后续的删除操作
# 没有匹配到key, 直接跳出此次循环
if key == None: break
# 找到了 key
tmp.append(key) # 临时队列添加当前的key
# 移除
del keys[pos]
# keys.pop(pos) 或这样删除
dict[key] -= 1 # 更新key的长度,已消费完成,删除来更新剩余个数
# 当全部消费完成,移除当前的任务
if dict[key] < 1:
del dict[key]
# 使用 join 方法连接列表中的字符串
result_str = ''.join(tmp)
result += result_str[::-1].rjust(n + 1, '-')[::-1]
# 边界的处理, 最后不要出现冷却时间
result = re.sub(r'-+$', '', result)
return len(result)
2 ) 方案2
class Solution:
def leastInterval(self, tasks: List[str], n: int) -> int:
# 初始化一个矩阵,用于存储给定任务的数量的字典
cnts = [0] * 26
for c in tasks:
cnts[ord(c) - ord('A')] += 1
# 统计出任务中对多的数量
max_cnt = max(cnts)
# 更新 tot 变量的值,用于统计具有最多执行次数的任务数量
tot = sum(1 for cnt in cnts if cnt == max_cnt)
# 这里是最核心的算法
return max(len(tasks), (n + 1) * (max_cnt - 1) + tot)
Golang 版算法实现
1 )方案 1
func leastInterval(tasks []byte, n int) int {
var result string // 最终队列执行的结果
dict := make(map[string]int) // 对归类进行存储
for _, item := range tasks {
// 将 item 转换为字符串类型
item := string(item)
if val, ok := dict[item]; ok {
dict[item] = val + 1
} else {
dict[item] = 1
}
}
// 无限循环
for {
// 任务清单
var keys []string
for key := range dict {
keys = append(keys, key)
}
// 任务处理完毕跳出
if len(keys) == 0 {
break
}
// 正常处理过程
tmp := make([]string, 0) // 用于存储 1 + n 个任务单元
// 在这里添加任务处理逻辑
for i := 0; i <= n; i++ {
maxVal := 0 // 找到最大的任务
var key string
var pos int
// 遍历任务清单
for idx, item := range keys {
// 当前任务数量大于 maxVal
if dict[item] > maxVal {
maxVal = dict[item] // 存储更新 maxVal
key = item // 存储更新当前任务
pos = idx // 存储更新当前任务下标索引,用于后续的删除操作
}
}
// 没有匹配到 key,直接跳出此次循环
if key == "" {
break
}
// 找到了 key
tmp = append(tmp, key) // 临时队列添加当前的 key
keys = append(keys[:pos], keys[pos+1:]...) // 将当前 key 从任务队列中删除
dict[key]-- // 更新 key 的长度,已消费完成,删除来更新剩余个数
// 当全部消费完成,移除当前的任务
if dict[key] < 1 {
delete(dict, key)
}
}
result += strings.Join(tmp, "") + strings.Repeat("-", n+1-len(tmp))
}
result = strings.TrimRight(result, "-")
return len(result)
}
2 )方案 2
import (
"math"
)
func leastInterval(tasks []byte, n int) int {
// 初始化一个数组,用于存储给定任务的数量的字典
cnts := make([]int, 26)
for _, c := range tasks {
cnts[c-'A']++
}
// 统计出任务中对多的数量
maxCnt := 0
for _, cnt := range cnts {
if cnt > maxCnt {
maxCnt = cnt
}
}
// 更新 tot 变量的值,用于统计具有最多执行次数的任务数量
tot := 0
for _, cnt := range cnts {
if cnt == maxCnt {
tot++
}
}
// 这里是最核心的算法
return int(math.Max(float64(len(tasks)), float64((n+1)*(maxCnt-1)+tot)))
}
3055



被折叠的 条评论
为什么被折叠?



