一筆書き (ヒアホルツァー法) で全部の切符を使う旅程を組み立てる

グラフ問題 117

片道の切符の束から、全部の切符をちょうど 1 回ずつ使う旅程を、行き先の名前が辞書の順で早い方を優先して組み立てます。行き止まりに着いた空港から後ろに並べていく一筆書きの練習です。

海外で定番の問題リスト 4 本には入っていません

問題

旅行の片道の航空券の束があります。切符の並び tickets を受け取ります。tickets[i] = [a, b] は、空港 a から空港 b へ飛ぶ切符です。空港は英大文字 3 字の名前で表します。空港 start から出発し、全部の切符をちょうど 1 回ずつ使う旅程を、通る空港の名前の並びで返す関数を書いてください。

  • そのような旅程は、少なくとも 1 つあります
  • 旅程がいくつかあるときは、空港の名前の並びを前から比べて、辞書の順でいちばん早い物を返します。たとえば ["HND", "CTS"] は ["HND", "ITM"] より先です
  • 同じ 2 つの空港のあいだの同じ向きの切符が、何枚かあることがあります
  • 切符は 1 枚以上 300 枚以下です

入力と出力の例

例 1

切符を順につなぐと、HND から CTS、CTS から ITM、ITM から FUK です。行き方はこの 1 通りです。

tickets = [["ITM", "FUK"], ["HND", "CTS"], ["CTS", "ITM"]] start = "HND"
["HND", "CTS", "ITM", "FUK"]

例 2

HND から先に CTS へ行くと、CTS から HND へ戻り、OKA へ行けます。先に OKA へ行くと、OKA から先の切符が無く、CTS の切符が残ります。辞書の順では CTS が先で、そのまま全部を使えます。

tickets = [["HND", "OKA"], ["HND", "CTS"], ["CTS", "HND"]] start = "HND"
["HND", "CTS", "HND", "OKA"]

例 3

HND から辞書の順で早い CTS へ行くと、CTS から先の切符が無く、ほかの切符が残ります。全部を使うには、先に KIX へ行って HND へ戻り、最後に CTS へ行きます。辞書の順で早い方を選ぶだけでは、行き止まりに入ることがあります。

tickets = [["HND", "CTS"], ["HND", "KIX"], ["KIX", "HND"]] start = "HND"
["HND", "KIX", "HND", "CTS"]

面接での聞かれ方

面接では、短く次のように渡されることが多いです。

  • 「航空券の出発地と到着地の組が渡されます。JFK から出発して全部の切符を使う旅程を、辞書順で最小の物で返してください」
  • 辞書の順で早い方へ進むだけの書き方を出すと、「行き止まりに入ったらどうしますか?」と聞かれます
  • 書き終えると「なぜ後ろから並べると正しいのですか?」と、一筆書きの考え方を説明できるかを見られます

同じ切符が何枚かあるか、全部を使う旅程が必ずあるかを最初に確かめておくと、行き止まりの扱いが決まります。

ヒント

まだ開いていません

付箋は 4 枚。下の段ほど答えに近づきます

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。

グラフ探索は、つながりを近い順か奥からたどります。迷路、最短の手数、講座を受ける順のような問題が同じ形です。

main.py
def itinerary(tickets: list[list[str]], start: str) -> list[str]:
    """
    Goal: 何を返すか

    idea1: まず思いつく案
    -> runtime: O(?)  space: O(?)

    idea2: もっと速い案
    -> runtime: O(?)  space: O(?)

    edge case: 空、1 つだけ、など
    """
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
print(itinerary([["ITM", "FUK"], ["HND", "CTS"], ["CTS", "ITM"]], "HND"))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します