面试题35. 复杂链表的复制
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。
我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
思路1:回溯
将链视作一个图
"""
# 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 __init__(self):
# 建立字典(哈希映射),将旧节点作为键,新节点作为值
self.visitedHash={}
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
# 如果已经复制过当前节点,那么只需返回它的复制版本
if head in self.visitedHash:
return self.visitedHash[head]
# 如果没有复制国当前节点,那么创建一个和当前节点值相同的节点
node = Node(head.val, None, None)
# 将复制的节点加入到字典(哈希映射)中,这可以防止因为随机指针的随机性原因可能会出现的循环
self.visitedHash[head] = node
# 下一个指针开始,从随机指针开始,递归的复制余下的链表
node.next = self.copyRandomList(head.next)
node.random = self.copyRandomList(head.random)
return node
思路2:O(N) 空间的迭代
迭代算法不需要将链表视为一个图。当在迭代链表时,只需为random指针和next指针指向的未访问过节点创造新的节点并赋值。
算法:
-
从head节点开始遍历链表。下图中,首先创建新的head拷贝节点。拷贝的节点如下图虚线所示。实现中,将新建节点的引用页放入已访问字典中。
-
random指针
*如果当前节点i的random指针指向一个节点j,且节点j已经被拷贝过,那么直接使用已访问字典中该节点的引用而不会新建节点。
*如果当前节点i的random指针指向一个节点j还没有被拷贝过,就到j节点创建对应的新节点,并把它放入已访问节点字典中下图中, A 的 random 指针指向的节点 C 。前图中可以看出,节点 C 还没有被访问过,所以我们创造一个拷贝的 C’节点与之对应,并将它添加到已访问字典中。
3. next指针
*如果当前节点i的next指针指向的节点j在已访问字典中已有拷贝,那么直接使用他的拷贝节点。
*如果当前节点i的random指针指向一个节点j还没有被访问过,创建一个对应节点的拷贝,并把它放入已访问节点字典中。下图中,A节点的 next 指针指向节点 B 。节点 B 在前面的图中还没有被访问过,因此我们创造一个新的拷贝 B’节点,并放入已访问字典中。
4. 重复步骤 2 和步骤 3 ,直到到达链表的结尾。
下图中, 节点 B 的 random 指针指向的节点 A 已经被访问过了,因此在步骤 2 中,不会创建新的拷贝,只将节点 B’的 random 指针指向克隆节点 A’。同样的, 节点 B 的 next 指针指向的节点 C 已经访问过,因此在步骤 3 中,不会创建新的拷贝,而直接将 B’ 的 next 指针指向已经存在的拷贝节点 C’。
class Solution:
def __init__(self):
# 建立字典(哈希映射),将旧节点作为键,新节点作为值
self.visited={}
def getCloneNode(self, node):
# 如果node存在,那么
if node:
# 检查该node是否被访问过
if node in self.visited:
# 如果它在访问的字典中,则从字典中返回新的节点引用
return self.visited[node]
else:
# 如果它没被访问过,创建这个引用在已访问数组中,并且返回它
self.visited[node] = Node(node.val, None, None)
return self.visited[node]
# 如果node不存在,返回None
return None
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
old_node = head
# 创建新的头节点
new_node = Node(old_node.val, None, None)
self.visited[old_node] = new_node
# 迭代链表,直到复制所有节点
while old_node != None:
# 得到复制的节点引用通过random和next指针
new_node.random = self.getCloneNode(old_node.random)
new_node.next = self.getCloneNode(old_node.next)
#在链表中向前移动一步
old_node = old_node.next
new_node = new_node.next
return self.visited[head]
思路3
O(1) 空间的迭代
与上面提到的维护一个旧节点和新节点对应的字典不同,我们通过扭曲原来的链表,并将每个拷贝节点都放在原来对应节点的旁边。这种旧节点和新节点交错的方法让我们可以在不需要额外空间的情况下解决这个问题。让我们看看这个算法如何工作
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head:
return head
# Creating a new weaved list of original and copied nodes.
ptr = head
while ptr:
# Cloned node
new_node = Node(ptr.val, None, None)
# Inserting the cloned node just next to the original node.
# If A->B->C is the original linked list,
# Linked list after weaving cloned nodes would be A->A'->B->B'->C->C'
new_node.next = ptr.next
ptr.next = new_node
ptr = new_node.next
ptr = head
# Now link the random pointers of the new nodes created.
# Iterate the newly created list and use the original nodes random pointers,
# to assign references to random pointers for cloned nodes.
while ptr:
ptr.next.random = ptr.random.next if ptr.random else None
ptr = ptr.next.next
# Unweave the linked list to get back the original linked list and the cloned list.
# i.e. A->A'->B->B'->C->C' would be broken to A->B->C and A'->B'->C'
ptr_old_list = head # A->B->C
ptr_new_list = head.next # A'->B'->C'
head_old = head.next
while ptr_old_list:
ptr_old_list.next = ptr_old_list.next.next
ptr_new_list.next = ptr_new_list.next.next if ptr_new_list.next else None
ptr_old_list = ptr_old_list.next
ptr_new_list = ptr_new_list.next
return head_old
上一篇: oracle [^[:print:]]无法过滤 非打印字符
下一篇: iOS防止内存泄漏