ヒープで 同じ 作業の 間を 空けて 全部を 終える 最短の 時間を求める
同じ
海外で定番の問題リスト 4 本には入っていません
問題
1 台のtasks をgap 単位の
- 器は
どんな 順で 焼いてもかまいません tasksの長さは 1 以上 10000 以下、 字は "A"から"Z"のどれかです gapは 0 以上 100 以下の整数です
入力と出力の例
例 1
A B C A B 休 A の
例 2
gap が 0 なら
例 3
A B C D A B C のように
面接での聞かれ方
面接では、
- 「CPU の
仕事の 並びと 冷却の 時間 n が あります。 同じ 仕事の あいだに n 単位を 空ける とき、 全部を 終える 最短の 時間は?」 - ヒープで
書くと 「どうして 残りの 多い 仕事から 入れるのですか?」と、 欲張りが 正しい 理由を 聞かれます - 書き終えた
あとに 「式 1 本で 求められますか?」と 聞かれる ことがあります
返すのが
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「スタックとキュー」に、
関わる学び
スタックは
まず 考える手順
段 1
並べ方を
段 2
いちばん困るのは、gap + 1 単位ずつの
段 3
「残りのgap + 1 個をgap + 1、
段 4
種類は 26 までなので、
解答
Python
Python
from collections import Counter
from heapq import heapify
from heapq import heappop
from heapq import heappush
def least_time(tasks, gap):
cnt = Counter(tasks)
h = [-c for c in
cnt.values()]
heapify(h)
time = 0
while h:
back = []
done = 0
while h and done <= gap:
c = -heappop(h) - 1
if c > 0:
back.append(c)
done += 1
for c in back:
heappush(h, -c)
if h:
done = gap + 1
time += done
return timeJavaScript
JavaScript
function leastTime(tasks, gap) {
const cnt = new Map();
for (const t of tasks) {
const c = cnt.get(t) ?? 0;
cnt.set(t, c + 1);
}
const h = [];
for (const [, c] of cnt) {
push(h, c);
}
const slot = gap + 1;
let time = 0;
while (h.length) {
const back = [];
let done = 0;
while (h.length) {
if (done === slot) {
break;
}
const c = pop(h) - 1;
if (c > 0) {
back.push(c);
}
done++;
}
for (const c of back) {
push(h, c);
}
if (h.length) {
done = slot;
}
time += done;
}
return time;
}
function push(h, x) {
h.push(x);
let i = h.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (h[p] >= h[i]) {
break;
}
[h[p], h[i]] = [h[i], h[p]];
i = p;
}
}
function pop(h) {
const top = h[0];
const last = h.pop();
if (h.length === 0) {
return top;
}
h[0] = last;
const n = h.length;
let i = 0;
while (true) {
let s = i;
const l = 2 * i + 1;
const r = l + 1;
if (l < n && h[l] > h[s]) {
s = l;
}
if (r < n && h[r] > h[s]) {
s = r;
}
if (s === i) {
return top;
}
[h[s], h[i]] = [h[i], h[s]];
i = s;
}
}Go
Go
import "container/heap"
type maxHeap []int
func (h maxHeap) Len() int {
return len(h)
}
func (h maxHeap) Less(
i, j int,
) bool {
return h[i] > h[j]
}
func (h maxHeap) Swap(
i, j int,
) {
h[i], h[j] = h[j], h[i]
}
func (h *maxHeap) Push(x any) {
*h = append(*h, x.(int))
}
func (h *maxHeap) Pop() any {
old := *h
x := old[len(old)-1]
*h = old[:len(old)-1]
return x
}
func leastTime(
tasks []string, gap int,
) int {
count := map[string]int{}
for _, t := range tasks {
count[t]++
}
h := &maxHeap{}
for _, c := range count {
heap.Push(h, c)
}
time := 0
for h.Len() > 0 {
back := []int{}
done := 0
for h.Len() > 0 &&
done <= gap {
c := heap.Pop(h).(int)
if c > 1 {
back = append(
back, c-1)
}
done++
}
for _, c := range back {
heap.Push(h, c)
}
if h.Len() > 0 {
time += gap + 1
} else {
time += done
}
}
return time
}取り出した物はback に
計算量
- 時間は O(n) です。
焼く 1 回ごとに ヒープの 出し入れが O(log 26) で、 26 は 決まった 数なので 定数です。 休みの 単位は 数えるだけで、 1 つずつは 回しません - 使う
記憶は O(1) です。 数える 入れ物も ヒープも、 大きさは 26 までです
よく ある間違い
- 取り出した
種類を すぐ ヒープに戻す。 同じ 区切りの 中で また 同じ 種類が 出てきて、 間の 決まりを 破ります。 区切りが 終わるまで 別の 所に貯めます - 最後の
区切りにも 休みを足す。 全部を 焼き終えた あとに 休みは 要りません。 最後の 区切りは 焼いた 数だけを 足します。 例 1 で 最後に 2 単位を 足すと 9 に なってしまいます - 区切りの
大きさを gapにする。 同じ種類の あいだに gap単位を空けるので、 1 つの 区切りは 焼く 1 単位を 足した gap + 1です - 並べ方を
全部作ろうとする。 返すのは 時間の 長さだけです。 順を 組み立てる 必要は ありません
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「残りの
多い 種類から 焼くのが 得なので、 種類ごとの 数を 大きい順の ヒープに 入れます。 時間を gap + 1 ずつの 区切りに 分け、 1 つの 区切りでは 違う 種類を 多い順に gap + 1 個まで 取り出して 1 つずつ 減らし、 区切りの 最後に 戻します。 まだ 残りが あれば 区切りの 長さを、 無ければ 焼いた 数だけを 足します。 種類は 26 までなので、 時間は O(n)、 記憶は O(1) です。」
書き終えたら、
「例 1 では、
数は A が 3、 B が 2、 C が 1 です。 1 つ目の 区切りで A、 B、 C を 焼き、 残りは A が 2、 B が 1 なので 3 を 足します。 2 つ目で A、 B を 焼き、 残りは A が 1 なので 3 を 足して 6 です。 3 つ目で A を 焼くと 全部終わるので、 焼いた 1 だけを 足して 7 です。」