1. 题目
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由
n
n
n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由
n
n
n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, index]
表示:
val
:一个表示Node.val
的整数。index
:随机指针指向的节点索引(范围从 0 0 0 到 n − 1 n-1 n−1);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
1.1 示例
- 示例
1
1
1 :
- 输入:
head = [[7, null], [13, 0], [11, 4],[10, 2], [1, 0]]
- 输出:
[[7, null], [13, 0],[11, 4], [10, 2], [1, 0]]
- 输入:
- 示例
2
2
2 :
- 输入:
head = [[1, 1], [2, 1]]
- 输出:
[[1, 1], [2, 1]]
- 输入:
-
示例 3 3 3 :
- 输入:
head = [[3, null], [3, 0], [3, null]]
- 输出:
[[3, null], [3, 0], [3, null]]
- 输入:
-
示例 4 4 4 :
- 输入:
head = []
- 输出:
[]
- 输入:
1.2 说明
- 来源: 力扣(LeetCode)
- 链接: https://leetcode-cn.com/problems/copy-list-with-random-pointer
1.3 限制
0 <= n <= 1000
-10000 <= Node.val <= 10000
Node.random
为空(null
)或指向链表中的节点。
2. 解法一(迭代+哈希表)
2.1 分析
此题乍一看似乎和其他链表类题目不太一样,其他链表类题目的要求其实都很简单直接,一般只要设置好辅助指针变量,然后在每一次迭代过程中正确地更新辅助指针的指向就可以总结出解法;然而,此题因为每个节点都有 random
指针的缘故,所以似乎很难设置并更新指针变量。
实际上,解答本题并不需要想得太复杂,题目要求我们复制链表那就直接迭代复制即可,因为单单根据节点的 next
指针其实就可以将链表的骨架复制出来了,除此之外我们只需要在复制过程中特别注意因节点 random
指针导致的影响即可。
具体地,在根据 next
指针进行链表复制时,在同时填充每个节点的 random
指针时,由于每个节点的 random
指针都可能指向链表的任何节点或 None
,这会导致两种可能的情况:
- 当前节点的
next
指针所希望指向的下一个节点可能在之前已经被创建过了,因为在之前的节点复制过程中,某些节点的random
指针需要指向该节点; - 当前节点的
random
指针所希望指向的节点更可能在之前也已经被创建过了,因为在之前的节点复制过程中,某些节点的random
指针或next
指针都可能需要指向该节点。
针对上述情况,如果不做特殊处理,那么就有可能导致在复制过程中重复创建 val
域相同的节点,而且通过 random
指针建立的链接关系也不会符合预期。
针对上述问题的解决方案也很简单,只要在复制过程中通过一个字典(其他语言一般称为映射)建立原链表和复制链表对应位置节点间的映射即可。
2.2 解答
from typing import Optional
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:
def copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return head
copy_map = {None: None}
copy_head = Node(head.val)
copy_map[head] = copy_head
cursor, copy_cursor = head, copy_head
while cursor:
if cursor.next not in copy_map.keys():
copy_cursor.next = Node(cursor.next.val)
copy_map[cursor.next] = copy_cursor.next
else:
copy_cursor.next = copy_map[cursor.next]
if cursor.random not in copy_map.keys():
copy_cursor.random = Node(cursor.random.val)
copy_map[cursor.random] = copy_cursor.random
else:
copy_cursor.random = copy_map[cursor.random]
cursor = cursor.next
copy_cursor = copy_cursor.next
return copy_head
需要指出的是,上述解答之所以用 copy_map = {None: None}
的形式来初始化所使用的映射,原因在于所有节点的 random
指针都可能指向 None
。
2.3 复杂度
- 时间复杂度: O ( n ) O(n) O(n) ;
- 空间复杂度: O ( n ) O(n) O(n) 。
2.4 代码优化
实际上,上述代码在分别填充当前节点的 next
指针和 random
指针时代码完全相同,仅迭代过程中针对的节点属性不同,下面使用 Python 内置的 getattr()
和 setattr()
方法对其进行了简化:
from typing import Optional
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:
def copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return head
copy_map = {None: None}
copy_head = Node(head.val)
copy_map[head] = copy_head
cursor, copy_cursor = head, copy_head
while cursor:
pointers = ['next', 'random']
for pointer in pointers:
if getattr(cursor, pointer) not in copy_map.keys():
val = getattr(getattr(cursor, pointer), 'val')
setattr(copy_cursor, pointer, Node(val))
copy_map[getattr(cursor, pointer)] = getattr(copy_cursor, pointer)
else:
setattr(copy_cursor, pointer, copy_map[getattr(cursor, pointer)])
cursor = cursor.next
copy_cursor = copy_cursor.next
return copy_head
上述代码还可进一步优化如下:
from typing import Optional
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:
def copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return head
copy_map = {None: None}
copy_head = Node(head.val)
copy_map[head] = copy_head
cursor, copy_cursor = head, copy_head
while cursor:
pointers = ['next', 'random']
for pointer in pointers:
attr = getattr(cursor, pointer)
if attr not in copy_map.keys():
val = getattr(attr, 'val')
setattr(copy_cursor, pointer, Node(val))
copy_map[attr] = getattr(copy_cursor, pointer)
else:
setattr(copy_cursor, pointer, copy_map[attr])
cursor = cursor.next
copy_cursor = copy_cursor.next
return copy_head
3. 解法二(迭代+时间换空间)
3.1 分析
实际上,也可以不通过额外的映射存储原链表和复制链表对应位置节点的关系来解答该题。具体地,可以通过以下步骤进行实现:
- 首先,通过节点的
next
指针修改原链表,使得原链表和复制链表对应位置的节点呈交替状,例如:原链表为A --> B --> C --> D
,则所谓呈交替状的链表为A --> A' --> B --> B' --> C --> C' --> D --> D'
; - 其次,为了准确地将复制节点的
random
指针进行正确链接,可以利用上述节点呈交替状的链表结构; - 最后,通过类似【LeetCode 单链表专项】奇偶链表(328)中的方法,实现原链表和复制链表之间节点的分离。
针对上述 3 3 3 个步骤,关于如何分别进行辅助指针变量的设置和更新,下面通过 3 3 3 组示意图进行描述:
- 复制原链表节点,使节点呈交替状:
下图的虚线框分别表示了第一次迭代和最后一次迭代之后链表的状态。
- 根据呈交替状链表结构,链接
random
指针:
下面 3 3 3 张图分别表示链接random
指针的第 1 1 1 次、第 2 2 2 次和最后一次迭代的过程或结果。
第 1 1 1 次迭代:
第 2 2 2 次迭代
最后一次迭代:
- 分离原链表和复制链表节点:
下面两张图分别表示第 1 1 1 次和最后一次迭代。
第 1 1 1 次迭代:
最后一次迭代:
3.2 解答
from typing import Optional
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:
def copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return head
cursor = head
while cursor:
succ = cursor.next
cursor.next = Node(cursor.val)
cursor = cursor.next
cursor.next = succ
cursor = succ
odd, even = head, head.next
while True:
if odd.random:
even.random = odd.random.next
else:
even.random = None
odd = even.next
if not odd:
break
else:
even = odd.next
even_head = head.next
odd, even = head, even_head
while True:
odd.next = even.next
odd = odd.next
if not odd:
break
even.next = odd.next
even = even.next
return even_head
上述
3
3
3 个 while
循环分别表示上述三个步骤,实际上虽然上述解法没有使用额外的哈希表,但是相较于第一种解法多出了两次遍历,因此我们说这是典型的以时间换空间。
3.3 复杂度
- 时间复杂度: O ( n ) O(n) O(n) ;
- 空间复杂度: O ( 1 ) O(1) O(1) 。
4. 解法四(使用标准模块)
实际上,如果允许使用 Python 的标准模块 copy
,那么一行代码就可以解决问题,即使用 copy.deepcopy()
函数:
# Definition for a Node.
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:
def copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
return copy.deepcopy(head)