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]
}
posted @ 2021-12-18 22:47  Simon_X  阅读(119)  评论(0编辑  收藏  举报