ベルマン・フォード法で乗り継ぎ k 回までのいちばん安い運賃を求める

都市を結ぶ便の運賃の並びから、乗り継ぎを k 回までに抑えて出発地から目的地へ行く、いちばん安い運賃を求めます。使う便の数を 1 本ずつ増やしながら運賃を緩めていく、回数に上限のある最短経路の練習です。

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

問題

n か所の都市があり、0 から n - 1 の番号が付いています。便の並び flights を受け取ります。flights[i] = [a, b, p] は、都市 a から都市 b へ運賃 p で飛ぶ片道の便です。

都市 src から都市 dst へ、乗り継ぎを k 回まで (つまり便を k + 1 本まで) 使って行くとき、運賃の合計のいちばん安い値を返す関数を書いてください。行けないなら -1 を返します。

  • n は 2 以上 100 以下、運賃 p は 1 以上 10000 以下の整数です
  • 同じ a から同じ b への便は 2 本ありません。自分の都市へ戻る便もありません
  • src と dst は違う都市です。k は 0 以上、n より小さい整数です

入力と出力の例

例 1

0 から 3 へは、直行便 700、1 で乗り継ぐ 100 + 200 = 300、1 と 2 で乗り継ぐ 100 + 50 + 100 = 250 の道があります。乗り継ぎ 1 回までなので、2 回乗り継ぐ 250 は使えず、300 です。

flights = [[0, 1, 100], [1, 2, 50], [2, 3, 100], [1, 3, 200], [0, 3, 700]] n = 4 src = 0 dst = 3 k = 1
300

例 2

例 1 と同じ便で、乗り継ぎ 2 回までなら 250 です。

flights = [[0, 1, 100], [1, 2, 50], [2, 3, 100], [1, 3, 200], [0, 3, 700]] n = 4 src = 0 dst = 3 k = 2
250

例 3

0 から 2 へは 1 で乗り継ぐしかありませんが、乗り継ぎは 0 回までです。

flights = [[0, 1, 10], [1, 2, 10]] n = 3 src = 0 dst = 2 k = 0
-1

面接での聞かれ方

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

  • 「都市と便の運賃が渡されます。乗り継ぎ k 回以内で src から dst へ行くいちばん安い運賃を返してください」
  • ダイクストラ法で書くと「乗り継ぎの上限があるとき、いちばん安い所から確定してよいですか?」と聞かれます
  • 「計算量は? 便の数と k のどちらで決まりますか?」と聞かれます

乗り継ぎの数え方 (k 回の乗り継ぎは便 k + 1 本) を最初に確かめておくと、繰り返しの回数を 1 回ずらす間違いを防げます。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

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

main.py
def cheapest_fare(flights: list[list[int]], n: int, src: int, dst: int, k: int) -> int:
    """
    Goal: 何を返すか

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

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

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


# 「実行」を押すと、この行の結果が出ます
print(cheapest_fare([[0, 1, 100], [1, 2, 50], [2, 3, 100], [1, 3, 200], [0, 3, 700]], 4, 0, 3, 1))

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