元の節と写しの対応表で random つきの連結リストを丸ごと写す

次の節のほかに、同じリストのどこかの節を指す random を持つ連結リストを、全部新しい節で写して返します。元の節から写しの節を引ける対応表を先に作り、つなぎ先を 2 回目でそろえる練習です。

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

問題

連結リストの先頭 head を受け取ります。どの節も、次の節を指す next のほかに、同じリストのどれかの節を指す random を持ちます。このリストと同じ形のリストを、全部新しく作った節で組み立て、その先頭の節を返す関数を書いてください。

  • 各節は val (値)、next (次の節)、random (同じリストのどれかの節) を持ちます。random は自分自身を指すことも、どの節も指さないこともあります。指さないときと最後の節の next は、Python では None、JavaScript では null、Go では nil です
  • Go の節は ListNode で、Val、Next、Random と大文字で始まります。新しい節は、Python では ListNode(値)、JavaScript では new ListNode(値)、Go では &ListNode{Val: 値} で作ります
  • 写しの節の next と random は、写しの中の節を指すようにします。受け取った節を 1 つでも写しに混ぜたり、写しの random が元のリストの節を指したりしたら誤りです
  • リストが空なら空のまま返します。受け取ったリストは書き換えないでください
  • 節の数は 0 以上 100 以下、値はどれも -10000 以上 10000 以下です。同じ値の節が何度出てもかまいません

例では、リストを先頭から読み、節ごとに [値, random の番号] と書きます。番号は先頭を 0 とした何番目の節か、random がどの節も指さなければ null です。

入力と出力の例

例 1

先頭の 5 は最後の 4 を、最後の 4 は先頭の 5 を指します。まん中の 9 の random はどこも指しません。

head = [[5, 2], [9, null], [4, 0]]
[[5, 2], [9, null], [4, 0]]

例 2

値 8 の節が 2 つあります。値ではなく、どの節を指すかで写します。値 1 の節は自分自身を指します。

head = [[8, 3], [1, 1], [8, null], [2, 0]]
[[8, 3], [1, 1], [8, null], [2, 0]]

例 3

節が 1 つも無いリストです。

head = []
[]

面接での聞かれ方

連結リストと対応表 (ハッシュ表) を組み合わせる問題として、よく出ます。

  • 「次の節と、どこか別の節を指す節でできたリストがあります。丸ごと写してください」
  • 「画面の部品の木やゲームの盤面のように、互いを指し合う物を写す (深い写し) とき、どう書きますか?」と、身近な形で聞かれることもあります
  • 書き終えたあとに「対応表を使わずに、追加の記憶 O(1) で書けますか?」と続きを聞かれることがあります

ミドルの問題なので、random の行き先がまだ作っていない節かもしれない、という所に気づき、作る回とつなぐ回を分けられるかが見られます。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

アルゴリズムのチートシートの「配列と文字列」に、この型の要点を表とコードでまとめています。

配列と文字列の問題は、全部の組を調べると手間が大きくなる所を、窓や左右の添字で減らす形が多いです。

main.py
def copy_random_list(head: ListNode | None) -> ListNode | None:
    """
    Goal: 何を返すか

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

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

    edge case: 空、1 つだけ、など
    """
    # 連結リストの節は val、next を持ちます。最後の next は None です
    # random は同じリストのどれかの節か None です
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
# build_list は [値, random の番号] の並びから組みます (None は random なし)
print(list_values(copy_random_list(build_list([[5, 2], [9, None], [4, 0]]))))

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