元の 節と 写しの 対応表で random つきの 連結リストを 丸ごと写す
次の
海外で定番の問題リスト 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 の番号] とrandom がnull です。
入力と出力の例
例 1
先頭の 5 は
例 2
値 8 の
例 3
節が 1 つも
面接での聞かれ方
連結リストと
- 「次の
節と、 どこか別の 節を 指す節で できたリストが あります。 丸ごと 写してください」 - 「画面の
部品の 木や ゲームの 盤面のように、 互いを 指し合う物を 写す (深い 写し) とき、 どう 書きますか?」と、 身近な形で 聞かれる こともあります - 書き終えた
あとに 「対応表を 使わずに、 追加の 記憶 O(1) で 書けますか?」と 続きを 聞かれる ことがあります
ミドルの
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「配列と文字列」に、
関わる学び
配列と
まず 考える手順
段 1
1 回なめて、next もつなぐnext はrandom で、
段 2
そこで、
段 3
2 回目に、p のnext はp.next のrandom はp.random の
段 4
next や random がnil が
解答
Python
Python
def copy_random_list(head):
m = {None: None}
p = head
while p:
m[p] = ListNode(p.val)
p = p.next
p = head
while p:
c = m[p]
c.next = m[p.next]
c.random = m[p.random]
p = p.next
return m[head]JavaScript
JavaScript
function copyRandomList(head) {
const m = new Map();
m.set(null, null);
let p = head;
while (p !== null) {
m.set(p,
new ListNode(p.val));
p = p.next;
}
p = head;
while (p !== null) {
const c = m.get(p);
c.next = m.get(p.next);
c.random = m.get(p.random);
p = p.next;
}
return m.get(head);
}Go
Go
func copyRandomList(
head *ListNode,
) *ListNode {
m := map[*ListNode]*ListNode{}
p := head
for p != nil {
m[p] = &ListNode{Val: p.Val}
p = p.Next
}
p = head
for p != nil {
c := m[p]
c.Next = m[p.Next]
c.Random = m[p.Random]
p = p.Next
}
return m[head]
}m がp はc は p のnil) をnil が
計算量
- 時間は O(n) です。
n は 節の 数で、 元の リストを 2 回なめ、 表を 引くのも 入れるのも 1 回あたり O(1) です - 使う
記憶は O(n) です。 返す写しの 節の ほかに、 n 組の 対応表を 持ちます。 元の 節の すぐ 後ろに 写しの 節を 差し込んでから 切り離す 書き方なら、 写しの ほかの 記憶を O(1) にできます
よく ある間違い
- 1 回なめるだけで random もつな
ごうとする。 random の 行き先が 後ろの 節だと、 その 写しは まだ ありません。 先に 全部の 節を 写してから、 2 回目でつなぎます - 値で
対応を取る。 例 2 のように 同じ値の 節が あると、 どちらの 8 を 指すのかが 決まりません。 対応表の 鍵は 値ではなく、 節その ものにします - random に
元の 節を そのまま入れる。 c.random = p.randomと書くと、 写しが 元の リストを 指したままに なります。 元の リストを 書き換えると 写しまで 変わって 見え、 深い 写しに なっていません。 必ず 表を 引いて 写しの 節を入れます - どこも
指さない ときの 扱いを忘れる。 Python で m[None]を入れ忘れると、 最後の 節の nextや、random の 無い節で 表を 引いた 所で 止まります
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「random の
行き先は、 まだ 写していない 後ろの 節かもしれません。 同じ値の 節も あるので、 値では 対応が 取れません。 そこで 元の 節から 写しの 節を 引く 表を 作ります。 1 回目で 全部の 節の 写しを 値だけで 作って 表に 入れ、 2 回目で next と random を 表から 引いてつなぎます。 時間も 記憶も O(n) です。」
書き終えたら、
「例 1 で
たどります。 1 回目で 5、 9、 4 の 写しを 作り、 表に 入れます。 2 回目、 5 の 写しの next は 9 の 写し、 random は 4 の 写しです。 9 の 写しの random は どこも 指しません。 4 の 写しの random は 5 の 写しです。 記憶を O(1) に したいなら、 元の 各節の すぐ 後ろに 写しを 差し込み、 random の 写しを 『元の random の 次の 節』で 決めてから、 2 本に 切り分けます。」