LeetCode LCP 32. 批量处理任务
贪心 + 二分
一开始没啥好思路, 先从模拟的角度思考。
假设有一条基准线l
不断从0往上推, 遇到的任务最小起始时间不得低于基准线l <= start
。直到start
无法移动,则分配时间执行任务。
[1,3,2] [2,3,2]
[2,5,3] -> [2,5,3] -> [4,5,1] ->
[5,6,2] [5,6,2] [4,6,2] [6,6,1]
对于每个可以计入总时间任务而言, 执行的结束时间似乎都是各任务的结束时间。
此时不妨贪心
地认为, 尽量让任务拖到最后执行, 结束时间越早的任务则越有可能先执行。
因此先按结束时间
升序排序, 再开始考虑任务执行。
[1,3,2]
[2,5,3]
[5,6,2]
- 执行
1,3,2
由于这个任务最早执行, 没有其他任务影响, [1,3]
相对2
来说似乎有些多余, 因此等价转换为执行了[2,3,2]
。
[2,3,2]
- 执行
[2,5,3]
任务[2,5]
的起始时间2 ∈ [2,3]
。执行代价会随上一个任务降低。首先降低执行代价, 得到[2,5,1]
。
继续根据上一步的贪心规则, 等价转换为[5,5,1]
既然每次任务执行都使用其实际执行代价, 那么不妨加上上一个执行代价, 数组表示转换为任务执行至[5,5]
时所花费的代价。
[2,3,2 ] [2,3,2]
[5,5,1 +2] -> [5,5,3]
- 执行
[5,6,2]
[2,3,2] [2,3,2 ] [2,3,2]
[5,5,3] [5,5,3 ] [5,5,3]
[5,6,2] -> [6,6,1 +3] -> [6,6,4]
此时主要的贪心
思路已经完成。新任务计算实际代价时, 由于旧任务有序, 可以使用二分搜索
找到可能的旧任务区间, 算出实际执行代价。
然而, 随着任务的不断增加, 旧任务区间可能不严格递增。
- 此时需要
区间压缩
(似乎能叫状态压缩?)
首次发生时, 旧任务区间必定有序。 由于实际执行代价分摊至旧任务区间的间隙, 新任务最后执行, 则最上层的旧区间必定能分摊执行代价。
直接从最上层开始不断压缩搜索区间, 二分搜索
的代价也会随着连续区间的增加而不断降低。(具体未精确计算, 懒)
以任务[4,5,2]
与[3,6,4]
为例
[4,5,2] [4,5,2] [4,5,2 ] [4,5,2] -> [3,6,4]
[3,6,4] -> [4,6,2] -> [4,6,2 +2] -> [4,6,4]
end
与执行代价
直接继承新任务, start
则新计算。
聪明如你, 一定发现了如果下点功夫利用输入数据, 就能做到空间复杂度O(1)。
(虽然增加边界可以让代码更直观, 但既然A了就不改了)
截止AC时, 执行用时564ms
, 内存消耗26.4MB
, 双100%, 在分布上相对头部数据也是够看的_(:з」∠)_
Go
func processTasks(tasks [][]int) int {
sort.Slice(tasks, func(i, j int) bool {
return tasks[i][1] < tasks[j][1]
})
i := 0
for _, v := range tasks {
if i <= 0 {
v[0] = v[1] - v[2] + 1
tasks[i] = v
i++
continue
}
last := tasks[i-1]
if last[1] < v[0] {
v[0] = v[1] - v[2] + 1
v[2] += last[2]
tasks[i] = v
i++
continue
}
j := 0
for count, half, middle := i, 0, 0; count > 0; {
half = count >> 1
middle = j + half
if tasks[middle][1] < v[0] {
j = middle + 1
count -= half + 1
continue
}
count = half
}
p, t := last[2], tasks[j]
if j > 0 {
p -= tasks[j-1][2]
}
if t[0] <= v[0] && v[0] <= t[1] {
p -= v[0] - t[0]
}
v[2] -= p
if v[2] <= 0 {
continue
}
v[0] = v[1] - v[2] + 1
v[2] += last[2]
tasks[i] = v
// 区间压缩
for ; i > 0 && tasks[i][0] <= tasks[i-1][1]; i-- {
copy(tasks[i-1][1:], tasks[i][1:])
tasks[i-1][0] = tasks[i-1][1] - tasks[i-1][2] + 1
if i-1 > 0 {
tasks[i-1][0] += tasks[i-2][2]
}
}
i++
}
if i <= 0 {
return 0
}
return tasks[i-1][2]
}