概述
此题同 剑指 Offer 35. 复杂链表的复制
这道题解法非常多
分析
如果没有random指针,那么就是普通的单链表
现在多出来的条件是,有一个random指针,要求我们把random指针也拷贝下来
random指针指向的是结点,不是index,如果是index就好办很多
题解
暴力 使用额外的存储空间
现在关键的地方是我们怎么把random指针指向的结点保存下来
由于这个自定义类是可变的,我们没办法做hash来记录index
一个暴力的方法是,用list来存储链表中每个结点,然后对每个random指针进行校对,得到所有random指针的下标,用下标信息来生成新的链表
大约是O(n^2)的时间复杂度,1000个结点的话应该能暴力通过
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
dh = Node(-1)
ini_lst = []
p = head
while p:
ini_lst.append(p)
p = p.next
ini_lst.append(None)
# 保存下标信息
ind = [0 for i in range(len(ini_lst)-1)]
for cur,p in enumerate(ini_lst):
if p == None:
break
rn = p.random
for i in range(len(ini_lst)):
if ini_lst[i] == rn:
ind[cur] = i # random指向结点的下标
break
new_lst = []
for i in range(len(ini_lst)-1):
new_lst.append(Node(ini_lst[i].val,None,None))
new_lst.append(None)
for i in range(len(ini_lst)-1):
new_lst[i].random = new_lst[ind[i]]
new_head = dh
for i in range(len(ini_lst)-1):
new_head.next = new_lst[i]
new_head=new_head.next
return dh.next
上面用了两个list,两个循环遍历保存random指向的下标,然后第二个list用来生成新的结点
时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度
O
(
n
)
O(n)
O(n)
如果我们用hash的话,会更加简洁
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return head
dct = {}
cur = head
# 原结点和新结点的映射
while cur:
dct[cur] = Node(cur.val)
cur = cur.next
# 新结点的指针处理
cur = head
while cur:
dct[cur].next = dct.get(cur.next)
dct[cur].random = dct.get(cur.random)
cur = cur.next
return dct[head]
主要是原结点和新结点的映射,还有第二个循环处理很巧妙
时间复杂度
O
(
n
)
O(n)
O(n)
空间复杂度
O
(
n
)
O(n)
O(n)
转换为图
还是第一次碰到这种写法
按照next指针和random指针可以构造出一个图,head仍然是图的入口
对图的搜索有两个版本,DFS和BFS
DFS
从头结点 head 开始拷贝;
由于一个结点可能被多个指针指到,因此如果该结点已被拷贝,则不需要重复拷贝;
如果还没拷贝该结点,则创建一个新的结点进行拷贝,并将拷贝过的结点保存在哈希表中;
使用递归拷贝所有的 next 结点,再递归拷贝所有的 random 结点。
作者:z1m
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/lian-biao-de-shen-kao-bei-by-z1m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
def dfs(head):
if not head:
return head
if head in visited:
return visited[head]
copy = Node(head.val)
visited[head] = copy
copy.next = dfs(head.next)
copy.random = dfs(head.random)
return copy
visited = {}
return dfs(head)
还是要用到hash,不难,就是转换为图遍历这个操作不太好想
BFS
懒得写了,直接copy
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
visited = {}
def bfs(head):
if not head: return head
clone = Node(head.val, None, None) # 创建新结点
queue = collections.deque()
queue.append(head)
visited[head] = clone
while queue:
tmp = queue.pop()
if tmp.next and tmp.next not in visited:
visited[tmp.next] = Node(tmp.next.val, [], [])
queue.append(tmp.next)
if tmp.random and tmp.random not in visited:
visited[tmp.random] = Node(tmp.random.val, [], [])
queue.append(tmp.random)
visited[tmp].next = visited.get(tmp.next)
visited[tmp].random = visited.get(tmp.random)
return clone
return bfs(head)
作者:z1m
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/lian-biao-de-shen-kao-bei-by-z1m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
copy和拆分
如果在原链表的每个结点后加上一个新的结点,那么random的copy就很容易了
A->A’->B->B’->C->C’->None
我们把pre作为旧结点,cur作为新结点
有以下关系:
cur.random = pre.random.next
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return head
cur = head
# 1. copy
while cur:
temp = Node(cur.val)
temp.next = cur.next
cur.next = temp
cur = temp.next
# 2. set random
pre,cur = head,head.next
while pre:
if pre.random == None:
cur.random = None
else:
cur.random = pre.random.next
pre = cur.next
if not pre:
break
cur = pre.next
# 3. get new link
cur = head.next
while cur.next:
cur.next = cur.next.next
cur = cur.next
return head.next
上面代码写得不够优雅,原链表会断链,我们修改一下
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return head
cur = head
# 1. copy
while cur:
temp = Node(cur.val)
temp.next = cur.next
cur.next = temp
cur = temp.next
# 2. set random
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next # key
cur = cur.next.next
# 3. get new link
pre,cur,ans = head,head.next,head.next
while cur.next:
pre.next = pre.next.next
cur.next = cur.next.next
pre = pre.next
cur = cur.next
pre.next = None
return ans
现在第二个循环写得更加符合特性,原链表也不会断链,nice
时间复杂度
O
(
n
)
O(n)
O(n)
空间复杂度
O
(
1
)
O(1)
O(1)
总结
一共介绍了3个方法
暴力(用list和用hash)
转换为图
copy和拆分
像这种题目呢,暴力比较好想到,做是能做的,但是更巧妙一点的解法就不好想到了