138. 复制带随机指针的链表(Copy List with Random Pointer)

难度:中等 | 主题:链表、哈希表

题目

给你一个长度为 n 的链表,每个节点包含一个额外随机指针 random,可指向链表中任意节点或空节点。构造这个链表的深拷贝

示例

 
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
 
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
 

思路

先讲个故事:复印一本带书签的书

你有一本旧书,书里有些页贴了彩色书签(random 指针),书签可能指向这本书的任意一页。你想复印一本一模一样的新书——不仅内容一样,书签位置也完全对应

问题在于:复印机印新书时,页码还没确定。你没法直接说”书签指向第 13 页”,因为新书的页码还没决定。


引导式推导:从哈希表到原地拼接

第 1 层:哈希表映射(直觉方案)

先复印所有空白页(只复制值),同时在一张对照表上记下”老页码 → 新页码”。第二遍再根据对照表填上书签。

 
第一遍:建映射
 
old → new 的哈希表
 
①→①' ②→②' ③→③'
 
第二遍:连指针
 
'.next = ①.next 对应的新节点
 
'.random = ①.random 对应的新节点
 

第 2 层:原地拼接(O(1) 空间,更优)

能不能省掉哈希表?可以——把新节点插在旧节点后面

 
原链表:① → ② → ③
 
拼接后:① → ①' → ② → ②' → ③ → ③'
 

这样旧节点和新节点就有了天然的对应关系:旧节点的 next 就是它的拷贝

设置 random 时:①'.random = ①.random.next

分离链表时:把奇数位置(旧)和偶数位置(新)拆开。


graph TB

    subgraph 三步法

        A["① → ①' → ② → ②' → ③ → ③'"] --> B["设 random:<br/>①'.random = ①.random.next"]

        B --> C["分离:<br/>旧: ①→②→③<br/>新: ①'→②'→③'"]

    end

    style C fill:#ffd700

解法时间空间推荐
哈希表O(n)O(n)✅ 稳妥
原地拼接O(n)O(1)进阶亮点

哈希表法更好写、不易错,推荐。原地拼接空间更优但易错。


代码

 
def copyRandomList(self, head):
 
    if not head:
 
        return None
 
    old_to_new = {}
 
    curr = head
 
    while curr:
 
        old_to_new[curr] = Node(curr.val)
 
        curr = curr.next
 
    curr = head
 
    while curr:
 
        if curr.next:
 
            old_to_new[curr].next = old_to_new[curr.next]
 
        if curr.random:
 
            old_to_new[curr].random = old_to_new[curr.random]
 
        curr = curr.next
 
    return old_to_new[head]
 

复杂度

指标解释
时间O(n)两次遍历
空间O(n)哈希表存 n 个映射

实战考量

频率分析

出现在:字节/微软/Amazon 常考,约 30% 的 AI Agent 二面会出这题。考察深拷贝概念和复杂引用关系的处理。

延伸思考

Q:能不能 O(1) 空间做?

A:可以。三步走——① 每个原节点后插拷贝节点 ② 设 random:①'.random = ①.random.next ③ 分离两链表。但实践中哈希表法更稳妥,原地拼接容易写错。

Q:如果链表有环呢?

A:哈希表法不受影响。原地拼接法需要额外处理(先判环再拷贝)。

Q:和克隆图(133 题)有什么区别?

A:完全一样的思路。图的每个节点也有”邻居列表”,相当于多个 random 指针。

Q:哈希表的 key 为什么用节点对象不用节点值?

A:节点值可能重复(如多个节点的 val=1),用值做 key 会导致映射冲突。

易错点

  • random 可能为 None,需判断

  • 哈希表的 key 是节点对象(内存地址),不是值

  • 返回的是新节点链表的头,不是旧节点


生活类比

深拷贝 → 照相 + 对照表

哈希表法就像给老书每页拍照,同时在相册封底记下”第 3 页照片在第 3 格”。原地拼接法就像把新页插在老页后面——想看老页对应的新页?翻到它后面那页就行。


相关题目

题目关系
133. 克隆图同族:深拷贝 + DFS/BFS 复制结构

→ 返回题单:LeetCode学习路线图 > 四、链表

速记卡(面试闪卡)

Q1:一句话讲清「138. 复制带随机指针的链表(Copy List with Random Pointer)」到底是什么?

A:深拷贝一条带随机指针的链表:新链表的 next 和 random 都要和原链表结构一一对应,且节点互不共享。

Q2:题目:复印一本带书签的奇书(deep copy) —— 怎么理解?

A:像复印一本每页夹了彩色书签(random 指针)的旧书——新书既要内容一样,书签指向的页码也要一一对应,而且两本书的页是分开的、不能共用一张纸。难点在于:印新书时新书的页码还没定,没法直接写「指第13页」。

Q3:思路一:哈希表对照表(hash map) —— 怎么理解?

A:先给每个老节点印一份空白复印件,在对照表记下「老节点→新节点」。第二遍按表把 next 和 random 连到对应的复印件。就像给每页拍照、封底写「第3页照片在第3格」,稳当不易错。时间 O(n),空间 O(n)。

Q4:思路二:原地拼接省空间(O(1) space) —— 怎么理解?

A:把每个拷贝节点插在原节点后面(①→①’→②→②’),旧节点的 next 天然就是它的拷贝。设 random 时用 ①’.random = ①.random.next,最后把奇数(旧)偶数(新)拆开。就像把新页夹在老页后,想找对应页翻它后面那页就行。空间降到 O(1),但易写错。

Q5:复杂度与实战易错(O(n)/O(n) vs O(1)) —— 怎么理解?

A:哈希表法 O(n) 时间 O(n) 空间,最稳推荐;原地拼接 O(1) 空间是进阶亮点。易错点:random 可能为 None 要先判;哈希 key 用节点对象而非值(值会重复冲突);返回的是新链表头不是旧头。若链表有环,哈希表法不受影响,原地拼接需先判环。

Q6:核心速记主线有哪些?

  • 题目:深拷贝带随机指针的链表,next/random 都要一一对应

  • 思路一:哈希表存 老→新 映射,两遍连指针(O(n) 空间)

  • 思路二:原地拼接插在旧节点后,拆奇偶(O(1) 空间)

  • 复杂度:时间 O(n),空间 O(n) 或 O(1)

  • 易错:random 判空、key 用节点对象、返回新头

口诀

A:随机指针链要拷,

哈希对照先建好;

两链互不共一道,

原地拼接空间少。

相关链接