一筆書き (ヒアホルツァー法) で 全部の 切符を 使う 旅程を 組み立てる
片道の
海外で定番の問題リスト 4 本には入っていません
問題
旅行のtickets をtickets[i] = [a, b] は、start から
- そのような
旅程は、 少なくとも 1 つあります - 旅程が
いくつか ある ときは、 空港の 名前の 並びを 前から 比べて、 辞書の 順で いちばん早い物を 返します。 たとえば ["HND", "CTS"]は["HND", "ITM"]より先です - 同じ 2 つの
空港の あいだの 同じ 向きの 切符が、 何枚か ある ことがあります - 切符は 1 枚以上 300 枚以下です
入力と出力の例
例 1
切符を
例 2
HND から
例 3
HND から
面接での聞かれ方
面接では、
- 「航空券の
出発地と 到着地の 組が 渡されます。 JFK から 出発して 全部の 切符を 使う 旅程を、 辞書順で 最小の 物で 返してください」 - 辞書の
順で 早い方 へ進むだけの 書き方を 出すと、 「行き止まりに 入ったら どうしますか?」と 聞かれます - 書き終えると
「な ぜ後ろから 並べると 正しいのですか?」と、 一筆書きの 考え方を 説明できるかを 見られます
同じ
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
空港を
段 2
ヒアホルツァー法を
段 3
行き止まりの
段 4
再帰の
解答
Python
Python
def itinerary(tickets, start):
# 行き先を辞書の逆の順に持つ
nxt = {}
order = sorted(tickets)
for a, b in reversed(order):
nxt.setdefault(a, [])
nxt[a].append(b)
route = []
stack = [start]
while stack:
top = stack[-1]
if nxt.get(top):
b = nxt[top].pop()
stack.append(b)
else:
stack.pop()
route.append(top)
return route[::-1]JavaScript
JavaScript
function itinerary(
tickets, start,
) {
// 空港は 3 字なので、2 つを
// つなげた字の順で並べられる
const keys = tickets.map(
([a, b]) => a + b);
// 辞書の逆の順にする
keys.sort().reverse();
const nxt = new Map();
for (const k of keys) {
const a = k.slice(0, 3);
if (!nxt.has(a)) {
nxt.set(a, []);
}
nxt.get(a).push(k.slice(3));
}
const route = [];
const stack = [start];
while (stack.length) {
const top = stack.at(-1);
const out = nxt.get(top);
if (out?.length) {
stack.push(out.pop());
} else {
route.push(stack.pop());
}
}
return route.reverse();
}Go
Go
import (
"slices"
"sort"
)
func itinerary(
tickets [][]string,
start string,
) []string {
// 行き先を辞書の逆の順に持つ
t := append(
[][]string{}, tickets...)
sort.Slice(t, func(
i, j int,
) bool {
if t[i][0] != t[j][0] {
return t[i][0] > t[j][0]
}
return t[i][1] > t[j][1]
})
nxt := map[string][]string{}
for _, e := range t {
nxt[e[0]] = append(
nxt[e[0]], e[1])
}
route := []string{}
stack := []string{start}
for len(stack) > 0 {
top := stack[len(stack)-1]
out := nxt[top]
n := len(out) - 1
if n >= 0 {
nxt[top] = out[:n]
stack = append(
stack, out[n])
continue
}
stack = stack[:len(stack)-1]
route = append(route, top)
}
slices.Reverse(route)
return route
}切符を
計算量
- 時間は O(E log E) です。
E は 切符の 数です。 並べ替えに O(E log E)、 その あとは どの 切符も 1 回積まれて 1 回取り出されるだけです - 使う
記憶は O(E) です。 行き先の 表、 スタック、答えです
よく ある間違い
- 辞書の
順で 早い方 へ進むだけにする。 例 3 で CTS の 行き止まりに 入り、 残りの 切符を 使えません - 答えを
前から並べる。 行き止まりの 空港は 旅程の 後ろに 来ます。 確定した 順に 足したら、 最後に 逆にします - 同じ
切符を 1 枚に まとめる。 同じ 向きの 切符が 2 枚 あれば、 2 回飛びます。 集合で 持つと 1 枚ぶん 足りなくなります - 行き先の
並びを 先頭から 取り出す。 配列の 頭から 外すと、 1 回で 全体を ずらして 遅くなります。 逆の 順に 並べて 末尾から 取り出します
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「全部の
切符を 1 回ずつ 使う 一筆書きなので、 ヒアホルツァー法を 使います。 空港ごとに 行き先を 辞書の 逆の 順で 持ち、 スタックの 上の 空港に 切符が 残っていれば、 いちばん早い 行き先を 積みます。 残っていなければ 取り出して 答えに 足します。 最後に 答えを 逆にします。 行き止まりが 自然に 後ろへ 回るので、 戻りながら試す必要が ありません。 時間は 並べ替えの O(E log E) です。」
書き終えたら、
「例 3 で、
スタックは HND から 始めます。 いちばん早い CTS を 積みますが、 CTS には 切符が 無いので 取り出して 答えに 足します。 HND に 戻ると KIX が 残っているので KIX、 さらに HND を 積みます。 HND には もう 切符が 無いので 取り出し、 KIX、 最初の HND も 取り出します。 答えは CTS、 HND、 KIX、 HND で、 逆に して HND、 KIX、 HND、 CTS です。」