重排使相同元素至少间隔k
621. 任务调度器
给定一个用字符数组表示的 CPU 需要执行的任务列表。
其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。
任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。
CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。
然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,
因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的最短时间。
示例 :
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B.
在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,
而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
提示:
任务的总个数为 [1, 10000]。
n 的取值范围为 [0, 100]。
分析
有一个明显的贪心策略,即优先安排数量大的任务。下边解法都是基于这个贪心策略。
模拟
先统计每个任务的数量。
以n+1个任务为一轮,同一轮中一个任务最多被安排一次。
每一轮中,将当前任务按照剩余次数降序排列,再选择剩余次数最多的n+1个任务一次执行
如果任务的种类 t 少于 n + 1 个,就只选择全部的 t 种任务,其余的时间空闲。
func leastInterval(tasks []byte, n int) int {
count := make([]int, 26)
for _, v := range tasks {
count[v-'A']++
}
sort.Sort(sort.Reverse(sort.IntSlice(count)))
result := 0
for count[0] > 0 {
for i := 0; i <= n && count[0] > 0; i++ {
result++
if i < 26 && count[i] > 0 {
count[i]--
}
}
sort.Sort(sort.Reverse(sort.IntSlice(count)))
}
return result
}
时间复杂度: O(result),给每个任务都安排了时间,因此时间复杂度和最终的答案成正比
空间复杂度: O(26)=O(1)
也可以用堆代替排序来做模拟:
func leastInterval(tasks []byte, n int) int {
h := prepareHeap(tasks)
result := 0
set := list.New()
for h.Len() > 0 {
for i := 0; i <= n; i++ {
if h.Len() == 0 && set.Len() == 0 {
return result
}
result++
if h.Len() == 0 { // 需要待命只到i==n
continue
}
t := heap.Pop(h).(int)
if t > 1 {
set.PushBack(t - 1)
}
}
for set.Len() > 0 {
heap.Push(h, set.Remove(set.Front()).(int))
}
}
return result
}
func prepareHeap(tasks []byte) *Heap {
count := make([]int, 26)
for _, v := range tasks {
count[v-'A']++
}
h := &Heap{}
for _, v := range count {
if v > 0 {
h.Push(v)
}
}
heap.Init(h)
return h
}
type Heap []int
func (h Heap) Len() int { return len(h) }
func (h Heap) Less(i, j int) bool { return h[i] > h[j] }
func (h Heap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *Heap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *Heap) Pop() interface{} {
n := len(*h)
x := (*h)[n-1]
*h = (*h)[:n-1]
return x
}
复杂度同上。
总结规律用公式计算
1.统计数量最大的任务的数量max;假设数组 [“A”,“A”,“A”,“B”,“B”,“C”],n = 2,A的频率最高,记为max = 3, 两个A之间必须间隔2个任务,才能满足题意并且是最短时间(两个A的间隔大于2的总时间必然不是最短), 因此执行顺序为: A->X->X->A->X->X->A,这里的X表示除了A以外其他字母,或者是待命,不用关心具体是什么。 max个A,中间有 max - 1个间隔,每个间隔需要搭配n个X,再加上最后一个A,所以总时间为 (max - 1) * (n + 1) + 1
2.要注意可能会出现多个频率相同且都是最高的任务,比如 [“A”,“A”,“A”,“B”,“B”,“B”,“C”,“C”],所以最后会剩下一个A和一个B, 因此最后要加上频率最高的不同任务的个数 maxCount
3.公式算出的值可能会比数组的长度小,如[“A”,“A”,“B”,“B”,“D”],n=1,遗漏了最后的 D(还可以有 E、F等),这种情况要取数组的长度
func leastInterval3(tasks []byte, n int) int {
// 统计每个任务的数量
count := [26]int{}
// 数量最大的任务的数量及个数
max, maxCount := 0, 0
for _, v := range tasks {
c := count[v-'A'] + 1
count[v-'A'] = c
if max < c {
max = c
maxCount = 1
} else if max == c {
maxCount++
}
}
result := (max-1)*(n+1) + maxCount
if result < len(tasks) {
result = len(tasks)
}
return result
}
时间复杂度:O(M),其中 M 是任务的总数,即 tasks 数组的长度。
空间复杂度:O(1)。
358. K 距离间隔重排字符串
给你一个非空的字符串 s 和一个整数 k,你要将这个字符串中的字母进行重新排列,
使得重排后的字符串中相同字母的位置间隔距离至少为 k。
所有输入的字符串都由小写字母组成,如果找不到距离至少为 k 的重排结果,请返回一个空字符串 “"。
示例 1:
输入: s = "aabbcc", k = 3
输出: "abcabc"
解释: 相同的字母在新的字符串中间隔至少 3 个单位距离。
示例 2:
输入: s = "aaabc", k = 3
输出: ""
解释: 没有办法找到可能的重排结果。
示例 3:
输入: s = "aaadbbcc", k = 2
输出: "abacabcd"
解释: 相同的字母在新的字符串中间隔至少 2 个单位距离。
分析
同问题621一般思路。
func rearrangeString(s string, k int) string {
if k <= 1 {
return s
}
result := []byte(s)
pairs := count(result)
cmp := func(i, j int) bool {
if pairs[i].count == pairs[j].count {
return pairs[i].char < pairs[j].char
}
return pairs[i].count > pairs[j].count
}
sort.Slice(pairs, cmp)
j := 0
for pairs[0].count > 0 {
for i := 0; i < k; i++ {
if pairs[0].count == 0 {
break
}
if i >= len(pairs) || pairs[i].count == 0 {
return ""
}
result[j] = pairs[i].char
j++
pairs[i].count--
}
sort.Slice(pairs, cmp)
}
return string(result)
}
type Pair struct {
count int
char byte
}
func count(s []byte) []Pair {
pairs := make([]Pair, 26)
for _, b := range s {
pairs[b-'a'].char = b
pairs[b-'a'].count++
}
return pairs
}
767. 重构字符串
给定一个字符串S,检查是否能重新排布其中的字母,使得两相邻的字符不同。
若可行,输出任意可行的结果。若不可行,返回空字符串。
示例 1:
输入: S = "aab"
输出: "aba"
示例 2:
输入: S = "aaab"
输出: ""
注意:
S 只包含小写字母并且长度在[1, 500]区间内。
分析
问题358变体,k==2的特例:
func reorganizeString0(s string) string {
return rearrangeString(s, 2)
}