← 返回题目列表

如何复制带随机指针的链表?

高频 困难 第 17 / 28 题 更新于 2026/07/29
链表随机指针深拷贝哈希表

简化版

带随机指针链表需要深拷贝每个节点,并正确复制 nextrandom。常见做法有两种:哈希表记录“原节点 -> 新节点”的映射,或把新节点插到原节点后面,再拆分链表;前者清晰,后者额外空间 O(1)。

详细版

哈希表法分两遍:第一遍为每个原节点创建新节点并放入 map;第二遍根据 map 设置新节点的 nextrandom

map[old] = copy
copy.next = map[old.next]
copy.random = map[old.random]

原地穿插法分三步:复制节点插到原节点后面;利用 old.random.next 设置新节点 random;最后把原链表和复制链表拆开。面试要强调这是深拷贝,不能让新链表节点指向旧链表节点。

完整版教学

一、难点在 random 可能指向任意节点

普通链表复制只需要顺着 next 走,边遍历边创建新节点即可。带 random 指针时,每个节点还可能指向链表中任意节点或 null。

A -> B -> C
A.random -> C
B.random -> A
C.random -> null

复制后,新 A 的 random 应该指向新 C,而不是旧 C。这就是深拷贝的核心。

记忆钩子:复制 random 时要找“旧指针对应的新节点”。

二、哈希表法最直观

第一遍遍历原链表,为每个旧节点创建一个新节点,建立映射。

Map<Node, Node> map = new HashMap<>();
Node cur = head;
while (cur != null) {
  map.put(cur, new Node(cur.val));
  cur = cur.next;
}

第二遍再补指针:

cur = head;
while (cur != null) {
  Node copy = map.get(cur);
  copy.next = map.get(cur.next);
  copy.random = map.get(cur.random);
  cur = cur.next;
}
return map.get(head);

map.get(null) 在很多语言里要自己处理,Java HashMap 可以返回 null。

三、为什么不能一遍简单复制

如果 A.random 指向 C,而遍历到 A 时 C 的新节点还没创建,就没法设置 A’ 的 random。

current A
A.random -> C
C copy not created yet

当然可以先留空以后补,但这本质还是需要某种“旧节点到新节点”的映射。哈希表就是最直接的映射结构。

时间 O(n),空间 O(n),面试可读性最好。

四、穿插法把映射藏在链表里

穿插法第一步:每个旧节点后面插入复制节点。

A -> B -> C

A -> A' -> B -> B' -> C -> C'

此时旧节点 A 的复制节点就是 A.next。这相当于不用哈希表也能找到“旧 -> 新”的映射。

代码思路:

Node cur = head;
while (cur != null) {
  Node copy = new Node(cur.val);
  copy.next = cur.next;
  cur.next = copy;
  cur = copy.next;
}

五、设置 random 和拆分链表

设置 random 时,如果 cur.random 不为空,那么 cur 的复制节点是 cur.nextcur.random 的复制节点是 cur.random.next

cur.next.random = (cur.random == null) ? null : cur.random.next;

最后拆分:

A -> A' -> B -> B' -> C -> C'

old:  A -> B -> C
copy: A' -> B' -> C'

拆分时既要恢复原链表,也要连好新链表。这个步骤是穿插法最容易断指针的地方。

六、两种方法怎么选

方法时间额外空间优点风险
哈希表O(n)O(n)清晰,不改原链表结构需要额外内存
穿插法O(n)O(1)空间省临时修改原链表,指针更绕

如果面试要求先写正确,哈希表法更稳。如果追问空间优化,再讲穿插法。

真实工程里如果原链表正在被其他线程读,穿插法临时改结构可能不安全。

七、常见误区与追问

  • 误区:复制 random 时可以直接指向旧节点。 深拷贝要求新链表内部自洽,不能指向旧链表节点。
  • 误区:只复制 next 就完成了。 random 是题目核心,必须复制到对应的新节点。
  • 误区:穿插法完全没有副作用。 它会临时改变原链表结构,不适合并发读场景。
  • 追问:哈希表法为什么要两遍? 第一遍建立所有旧到新的映射,第二遍才能安全设置任意 random。
  • 追问:穿插法为什么空间 O(1)? 映射关系由 old.next 隐式表示,不需要额外 map。
  • 追问:空 random 怎么处理? 新节点 random 也应为 null,不能访问 cur.random.next

八、加强记忆

复制随机链表记成“先建映射,再补指针”。哈希表法把旧节点到新节点的关系放进 map,最清楚;穿插法把复制节点插在旧节点后面,用 old.next 隐式表示映射,空间更省。无论哪种方法,核心都不能变:新链表的 next 和 random 必须指向新节点,不能偷指旧节点。