トポロジカルソートで 手持ちの 材料から 作れる 料理を 全部見つける
料理ごとに
海外で定番の問題リスト 4 本には入っていません
問題
料理のrecipes と、needs をneeds[i] は、recipes[i] をstock も
作った[] を
- 名前は
どれも 英小文字の 1 字以上 10 字以下です。 料理の 名前は、 どれも違います - 料理の
名前と、 手持ちの 材料の 名前は 重なりません - どの
料理も、 材料は 1 つ以上 あります。 1 つの 料理の 材料の 並びに、 同じ 名前は 2 回出てきません。 料理が 自分 自身を 材料に 使う ことは ありません stockにも、同じ 名前は 2 回出てきません recipesの長さは 1 以上 100 以下、 stockの長さは 0 以上 100 以下です
入力と出力の例
例 1
roux は flour と butter で
例 2
toast は bread で
例 3
soup と stew は、
面接での聞かれ方
面接では、
- 「レシピと
必要な 材料、 手持ちの 材料が 渡されます。 作れる レシピを 全部返してください。 レシピは ほかの レシピの 材料に なることがあります」 - 料理ごとに
再帰で 調べる 書き方を 出すと、 「料理どうしが 互いを 材料に していたら、 どうなりますか?」と 聞かれます - 書き終えると
「これは 何という アルゴリズムですか?」と、 トポロジカルソートと 結び 付けられるかを 見られます
返す順と、
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
料理ごとに
段 2
向きを
段 3
始めは、
段 4
例 3 の soup と stew は、
解答
Python
Python
from collections import deque
def possible_recipes(
recipes, needs, stock
):
# 料理ごとのそろっていない数
left = {}
pairs = []
for i in range(len(needs)):
r = recipes[i]
left[r] = len(needs[i])
for x in needs[i]:
pairs.append((x, r))
# 材料を使う料理
users = {}
for x, r in pairs:
users.setdefault(x, [])
users[x].append(r)
q = deque(stock)
made = []
while q:
x = q.popleft()
rs = users.get(x, [])
for r in rs:
left[r] -= 1
if left[r] == 0:
made.append(r)
q.append(r)
return sorted(made)JavaScript
JavaScript
function possibleRecipes(
recipes, needs, stock,
) {
// 料理ごとのそろっていない数
const left = new Map();
// 材料を使う料理
const users = new Map();
recipes.forEach((r, i) => {
const k = needs[i].length;
left.set(r, k);
for (const x of needs[i]) {
if (!users.has(x)) {
users.set(x, []);
}
users.get(x).push(r);
}
});
const q = [...stock];
const made = [];
for (const x of q) {
for (const r of
users.get(x) ?? []) {
const k = left.get(r) - 1;
left.set(r, k);
if (k === 0) {
made.push(r);
q.push(r);
}
}
}
return made.sort();
}Go
Go
import "sort"
func possibleRecipes(
recipes []string,
needs [][]string,
stock []string,
) []string {
// 料理ごとのそろっていない数
left := map[string]int{}
// 材料を使う料理
users := map[string][]string{}
for i, r := range recipes {
left[r] = len(needs[i])
for _, x := range needs[i] {
users[x] = append(
users[x], r)
}
}
q := []string{}
q = append(q, stock...)
made := []string{}
for i := 0; i < len(q); i++ {
x := q[i]
for _, r := range users[x] {
left[r]--
if left[r] == 0 {
made = append(made, r)
q = append(q, r)
}
}
}
sort.Strings(made)
return made
}JavaScript の sort は、for...of で、i で、
計算量
- 時間は O(N + M + R log R) です。
N は 材料の 並びの 合計の 長さ、 M は 手持ちの 材料の 数、 R は 料理の 数です。 どの 矢印も 1 回ずつた どり、 最後に 作れた 料理を 並べ替えます - 使う
記憶は O(N + M + R) です。 材料から 料理への 表と、 数と、列です
よく ある間違い
- 料理ごとに
再帰で 調べ、 途中の 印を 付けない。 例 3 のような 輪で 止まらなくなります - 作った
料理を 列に 入れない。 例 1 で roux を 作っても curry の 数が 減らず、 curry を 見落とします - 辞書の
順に 並べ替えずに返す。 作れた 順のままだと、 例 1 は ["roux", "curry"]になり、 決まりと違います - 手持ちの
材料を 列に 入れ忘れる。 料理だけを 列に 入れると、 始めに 何も 数が 減らず、 例 1 で 何も 作れません。 始めの 列は 手持ちの材料です
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「材料から
料理へ 矢印を 引いて、 トポロジカルソートで 解きます。 料理ごとに、 まだ そろっていない 材料の 数を 持ちます。 手持ちの 材料を 全部列に 入れ、 取り出した物を 使う 料理の 数を 1 減らし、 0 に なった 料理を 答えに 足して 列に 入れます。 輪に なった 料理は 0 に ならないので、 自然に 除かれます。 最後に 辞書の 順に 並べます。」
書き終えたら、
「例 1 で、
数は curry が 2、 roux が 2 です。 列の rice を 取り出すと curry が 1、 flour で roux が 1、 butter で roux が 0 に なり、 roux を 作って 列に 入れます。 roux を 取り出すと curry が 0 に なり、 curry も 作れます。 並べ替えて curry、 roux です。」