速い指と遅い指で並びの中の重なった数を探す

1 から n までの数が n + 1 個入った並びから、2 回以上出てくる数を、並びを書き換えず O(1) の記憶で返します。値を次の行き先と見て連結リストの輪を探す練習です。

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

問題

整数の並び nums を受け取ります。要素の数は n + 1 個で、どの値も 1 以上 n 以下です。2 回以上出てくる値はちょうど 1 種類あります。その値を返す関数を書いてください。

  • 重なった値は 2 回とは限らず、3 回以上出てくることもあります
  • nums を書き換えてはいけません (並べ替えも、印を付けるのも無しです)
  • 使う記憶は O(1) にしてください。集合や数を数える表は使えません
  • n は 1 以上 10000 以下です

入力と出力の例

例 1

座席の番号札 1 から 5 を 6 人に配ったら、同じ番号が 2 枚混ざっていました。4 番が 2 回出てきます。

nums = [3, 4, 1, 5, 4, 2]
4

例 2

重なった値が 3 回以上出てくることもあります。2 が 4 回出てきます。

nums = [2, 2, 2, 1, 2]
2

例 3

いちばん小さい n です。2 個とも 1 です。

nums = [1, 1]
1

面接での聞かれ方

条件を足していくと解き方が変わる問題として、ミドルの面接で出ます。

  • 「重なった数を探してください」とだけ聞かれ、集合で解くと「記憶を O(1) にできますか?」「並びを書き換えずに?」と条件を足されます
  • 並べ替える、値の位置に負の印を付ける、という答えは「書き換えない」で外されます
  • 値の範囲で二分探索する O(n log n) の書き方を答えると、「O(n) にできますか?」と続くことがあります

ミドルの問題なので、並びを連結リストと見なす発想と、輪の入口を見つける 2 段目の理由を説明できるかが見られます。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

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

main.py
def find_duplicate(nums: list[int]) -> int:
    """
    Goal: 何を返すか

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

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

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


# 「実行」を押すと、この行の結果が出ます
print(find_duplicate([3, 4, 1, 5, 4, 2]))

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