問題
講座が n 個reqs をreqs の 1 つ [a, b] は、n 個の
- 順が
何通りも ある ときは、 いま受けられる 講座の うち、 番号の いちばん 小さい 物から受けると 決めます。 答えは これで 1 つに 決まります - 全部を
受ける ことができない ときは、 空の 並び []を返します。 一部だけ受けられても、 空の並びです nは 1 以上 2000 以下、reqsの長さは 0 以上 5000 以下です - 決まりの
aとbは違う 番号で、 同じ 決まりは 2 回出てきません
入力と出力の例
例 1
最初に
入力
n = 5
reqs = [[4, 1], [2, 1],
[3, 4], [3, 2]]出力
[0, 1, 2, 4, 3]例 2
講座 0 は
入力
n = 5
reqs = [[1, 4], [0, 2]]出力
[2, 0, 3, 4, 1]例 3
講座 0 と 1 が
入力
n = 3
reqs = [[0, 1], [1, 0]]出力
[]面接での聞かれ方
面接では、
- 「では、
受ける 順番を 1 つ返してください。 できない ときは空で」 - 書き終えた
あとに 「順番が 何通りも ある とき、 番号の 小さい 順に そろえるには どうしますか?」 「その とき計算量は どう 変わりますか?」と 続けて 聞かれます - 「深さ優先で
書くなら、 どこで 並びに 足しますか?」と、 別の 書き方を 聞かれる こともあります
順が 1 つに
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは、
段 2
ただ、
段 3
ヒープからn に
段 4
ヒープのheapq、container/heap を
解答
Python
Python
import heapq
def course_order(n, reqs):
to = [[] for _ in range(n)]
need = [0] * n
for a, b in reqs:
to[b].append(a)
need[a] += 1
push = heapq.heappush
pop = heapq.heappop
ready = []
for c in range(n):
if need[c] == 0:
push(ready, c)
order = []
while ready:
c = pop(ready)
order.append(c)
for nx in to[c]:
need[nx] -= 1
if need[nx] == 0:
push(ready, nx)
if len(order) < n:
return []
return orderJavaScript
JavaScript
function heapPush(h, v) {
h.push(v);
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 heapPop(h) {
const top = h[0];
const last = h.pop();
if (h.length === 0) {
return top;
}
h[0] = last;
let i = 0;
while (true) {
let m = i;
const l = 2 * i + 1;
const r = l + 1;
if (l < h.length) {
if (h[l] < h[m]) m = l;
}
if (r < h.length) {
if (h[r] < h[m]) m = r;
}
if (m === i) return top;
[h[m], h[i]] = [h[i], h[m]];
i = m;
}
}
function courseOrder(
n, reqs
) {
const to = [];
const need = [];
for (let c = 0; c < n; c++) {
to.push([]);
need.push(0);
}
for (const [a, b] of reqs) {
to[b].push(a);
need[a]++;
}
const ready = [];
for (let c = 0; c < n; c++) {
if (need[c] === 0) {
heapPush(ready, c);
}
}
const order = [];
while (ready.length > 0) {
const c = heapPop(ready);
order.push(c);
for (const nx of to[c]) {
need[nx]--;
if (need[nx] === 0) {
heapPush(ready, nx);
}
}
}
if (order.length < n) {
return [];
}
return order;
}Go
Go
import "container/heap"
type minHeap []int
func (h minHeap) Len() int {
return len(h)
}
func (h minHeap) Less(
i, j int,
) bool {
return h[i] < h[j]
}
func (h minHeap) Swap(
i, j int,
) {
h[i], h[j] = h[j], h[i]
}
func (h *minHeap) Push(x any) {
*h = append(*h, x.(int))
}
func (h *minHeap) Pop() any {
old := *h
last := len(old) - 1
v := old[last]
*h = old[:last]
return v
}
func courseOrder(
n int, reqs [][]int,
) []int {
to := make([][]int, n)
need := make([]int, n)
for _, p := range reqs {
a, b := p[0], p[1]
to[b] = append(to[b], a)
need[a]++
}
ready := &minHeap{}
for c := 0; c < n; c++ {
if need[c] == 0 {
heap.Push(ready, c)
}
}
order := []int{}
for ready.Len() > 0 {
c := heap.Pop(ready).(int)
order = append(order, c)
for _, nx := range to[c] {
need[nx]--
if need[nx] == 0 {
heap.Push(ready, nx)
}
}
}
if len(order) < n {
return []int{}
}
return order
}Go の container/heap は、Len、Less、Swap、Push、Pop の 5 つをLess をheap.Pop は
計算量
- 決まりの
数を e と すると、 時間は O(n log n + e) です。 どの 講座も ヒープに 1 回入って 1 回出て、 そのたびに O(log n) かかります。 どの 決まりも 1 回だけ見ます - 使う
記憶は O(n + e) です。 講座ごとの 矢印の 並びと、 前提の 数と、 ヒープと、 返す並びです - 順を
問わなければ 待ち行列で O(n + e) に できます。 番号の 小さい 順に そろえる 分だけ、 log n が かかります
よく ある間違い
- 待ち行列のまま、
番号順に そろった つもりになる。 最初に 受けられる 講座を 小さい 順に 入れても、 あとから 受けられるようになった 講座は 後ろに 並びます。 例 2 のように、 途中で 小さい 番号が 受けられるようになる 場面で 順が崩れます - 受けられない
場合に、 途中までの 並びを返す。 輪が あると、 並びは nに届かないまま 終わります。 長さを 確かめずに 返すと、 守れない 順を 答えとして 渡してしまいます - 矢印の
向きを 逆にする。 [a, b]を a から b への矢印に すると、 返す順が ちょうど 逆に なります。 「b を 終えると a へ 進める」と 口で 確かめます
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「前提の
残りが 0 の 講座から 取り出すトポロジカルソートで 書きます。 番号の 小さい 物から 受ける 決まりなので、 待ち行列の 代わりに ヒープを 使い、 いつも いちばん 小さい 講座を 取り出します。 最後に 並びの 長さが n に 届かなければ 空を 返します。 時間は O(n log n + e) です。」
書き終えたら、
「例 2 で
たどります。 前提の 残りが 0 なのは 2、 3、 4 で、 小さい 2 を 受けます。 すると 0 の 残りが 0 に なって ヒープに 入り、 ヒープには 0、 3、 4 が 並びます。 いちばん 小さい 0 を 受け、 次に 3、 4 と 受けます。 4 を 終えると 1 が 入り、 最後に 受けて [2, 0, 3, 4, 1]です。待ち行列のままだと、 0 が 3 と 4 の 後ろに 並んで [2, 3, 4, 0, 1]になってしまいます。」
