問題
講座が n 個reqs をreqs の 1 つ [a, b] は、n 個の
- 講座は 1 つずつ
受け、 受け終えた 講座は 前提として 何度でも使えます nは 1 以上 2000 以下、reqsの長さは 0 以上 5000 以下です - 決まりの
aとbは違う 番号で、 同じ 決まりは 2 回出てきません
入力と出力の例
例 1
0、
入力
n = 3
reqs = [[1, 0], [2, 1]]出力
true例 2
講座 1、
入力
n = 4
reqs = [[1, 0], [2, 1],
[3, 2], [1, 3]]出力
false例 3
講座 3 は、
入力
n = 5
reqs = [[4, 1], [2, 1],
[3, 4], [3, 2]]出力
true面接での聞かれ方
面接では、
- 「依存の
決まりが あります。 全部を 終えられるかを 判定してください」 - 書き終えた
あとに 「できない 理由は 何ですか?」 「その 場合に、 輪に なっている 講座を 見つけられますか?」と 続けて 聞かれます - 「受ける
順番 その ものを 返してください」と、 続きの 問題へ 進むこともあります
決まり [a, b] の
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは、[a, b] を b から a への
段 2
輪を
段 3
待ち行列がn なら真です。n に
解答
Python
Python
from collections import deque
def can_finish(n, reqs):
to = [[] for _ in range(n)]
need = [0] * n
for a, b in reqs:
to[b].append(a)
need[a] += 1
queue = deque()
for c in range(n):
if need[c] == 0:
queue.append(c)
taken = 0
while queue:
c = queue.popleft()
taken += 1
for nx in to[c]:
need[nx] -= 1
if need[nx] == 0:
queue.append(nx)
return taken == nJavaScript
JavaScript
function canFinish(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 queue = [];
for (let c = 0; c < n; c++) {
if (need[c] === 0) {
queue.push(c);
}
}
let head = 0;
while (head < queue.length) {
const c = queue[head];
head++;
for (const nx of to[c]) {
need[nx]--;
if (need[nx] === 0) {
queue.push(nx);
}
}
}
return head === n;
}Go
Go
func canFinish(
n int, reqs [][]int,
) bool {
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]++
}
queue := []int{}
for c := 0; c < n; c++ {
if need[c] == 0 {
queue = append(queue, c)
}
}
head := 0
for head < len(queue) {
c := queue[head]
head++
for _, nx := range to[c] {
need[nx]--
if need[nx] > 0 {
continue
}
queue = append(queue, nx)
}
}
return head == n
}JavaScript と Go は、head をhead と n を
計算量
- 決まりの
数を e と すると、 時間は O(n + e) です。 どの 講座も 待ち行列に 1 回だけ 入り、 どの 決まりも、 前提の 講座を 取り出した ときに 1 回だけ見ます - 使う
記憶は O(n + e) です。 講座ごとの 矢印の 並びと、 前提の 数と、 待ち行列です
よく ある間違い
- 矢印の
向きを 逆にする。 [a, b]を a から b への矢印に すると、 前提の 数を 数える 講座が ずれます。 今回の 判定の 答えは 向きを 逆に しても 変わりませんが、 順番を 返す 続きの 問題で そのまま 使うと、 逆の 順に なります。 「b を 終えると a へ 進める」と 口で 確かめます - 前提の
数を 0 に なった ときではなく、 減らすたびに入れる。 前提が 2 つある 講座を、 1 つ 終えた 時点で 待ち行列へ 入れると、 まだ 受けられない 講座を 受けたことになります。 0 に なった 瞬間に 1 回だけ入れます - 深さ優先で、
見ている 途中と 見終えたを 分けない。 再帰で 輪を 探す 書き方では、 「いまたどっている途中」の 印と 「もう調べ終えた」の 印を 分けないと、 輪ではない合流を 輪と 取り 違えます。 講座が 2000 個並ぶと Python の 再帰の 上限にも かかるので、 ここでは 待ち行列で書きます
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「講座を
点、 決まりを 前提から 次の 講座への 矢印と 見ると、 受けられないのは 輪が ある ときです。 前提の 残りが 0 の 講座から 待ち行列で 取り出し、 取り出すたびに 次の 講座の 残りを 減らします。 最後に 受けた 数が n に 届けば真です。 時間も 記憶も O(n + e) です。」
書き終えたら、
「例 2 で
たどります。 前提の 数は、 講座 0 が 0、 講座 1 が 2、 講座 2 と 3 が 1 です。 最初に 0 だけを 取り出し、 講座 1 の 数が 1 に 減ります。 ここで 待ち行列が 空に なり、 受けた数は 1 で 4 に 届かないので 偽です。 残った 1、 2、 3 が 輪です。」
