跳到主要内容

0138. 随机链表的复制

题目描述

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。深拷贝应该正好由 n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

示例

示例 1:

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

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

解题思路

第一步:理解难点

普通链表复制很简单,但这道题的 random 指针可能指向后面的节点,甚至形成环。复制时没法一次确定 random 指向哪个新节点。

第二步:暴力解法

每次复制一个节点时,再遍历原链表找到 random 对应的新节点。时间复杂度 O(n²)

第三步:优化解法 - 哈希表映射

第一遍遍历:创建所有新节点,并建立「原节点 -> 新节点」的映射。

第二遍遍历:通过映射表,连接新节点的 nextrandom

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)(哈希表)

第四步:最优解法 - 交错链表

利用原链表的空间作为映射:

  1. 第一遍:在每个原节点后面插入它的复制节点
  2. 第二遍:根据原节点的 random,设置复制节点的 random
  3. 第三遍:将交错链表拆分为两个独立链表
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(除了结果链表外)

完整代码实现

class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random

class Solution:
"""
138. 随机链表的复制 - 交错链表法

核心思想:
通过在原链表中插入复制节点,隐式建立原节点和复制节点的对应关系,
无需额外哈希表即可 O(1) 空间完成复制。

时间复杂度:O(n)
空间复杂度:O(1)(除结果外)
"""

def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None

# 步骤1:复制节点,插入到原节点后面
cur = head
while cur:
cur.next = Node(cur.val, cur.next)
cur = cur.next.next

# 步骤2:设置复制节点的 random
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next

# 步骤3:分离链表
dummy = Node(0)
tail = dummy
cur = head
while cur:
copy = cur.next
tail.next = copy
cur.next = copy.next
cur = cur.next
tail = tail.next

return dummy.next

示例推演

[[7,null],[13,0],[11,4],[10,2],[1,0]] 为例。

原链表:7 -> 13 -> 11 -> 10 -> 1

步骤1 - 插入复制节点后7 -> 7' -> 13 -> 13' -> 11 -> 11' -> 10 -> 10' -> 1 -> 1'

步骤2 - 设置 random

  • 原节点 7 的 random 为 null,7' 的 random 也为 null
  • 原节点 13 的 random 指向索引 0(即 7),13' 的 random 指向 7'
  • 原节点 11 的 random 指向索引 4(即 1),11' 的 random 指向 1'
  • 以此类推

步骤3 - 分离链表

  • 原链表恢复:7 -> 13 -> 11 -> 10 -> 1
  • 新链表:7' -> 13' -> 11' -> 10' -> 1'

复杂度分析

解法时间复杂度空间复杂度说明
暴力O(n²)O(1)每次找 random 都遍历
哈希表O(n)O(n)直观易懂
交错链表O(n)O(1)最优解

易错点总结

1. 空链表判断

如果 headNone,直接返回 None

2. random 为 null 的处理

在步骤2中,cur.random 可能为 None,必须加 if 判断,否则访问 cur.random.next 会报错。

3. 恢复原链表

步骤3中 cur.next = copy.next 这一步很重要,确保原链表结构不被破坏。


扩展思考

为什么这道题常考?

它同时考察了链表操作(插入、拆分)和复杂数据结构的复制,是链表类题目的巅峰之一。

相关题目

加载评论中...