Rearch Interest: Visualization">
剑指 Offer 35. 复杂链表的复制
题目描述
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个
next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
示例:
输入:
head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
输入:
head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
输入:
head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
输入:
head = []
输出:[]
提示:
-10000 <= Node.val <= 10000
Node.random
为空(null
)或指向链表中的节点。- 节点数目不超过
1000
。
我的思路
由于链表中的值不唯一、链表只能从前往后依次遍历,再加上random
可能指向当前节点的前方/后方/null
的特性,
考虑使用map
和哈希表来实现。 思路是,考虑在常规链表复制
(一次遍历) 的基础上,如何复制random
指针。
由于原始链表的每个节点和常规链表的每个节点都会对应新的数据域,
且链表复制的时候对于random
部分,本质上是需要对链表内的各个节点之间的联系进行复制,
因此希望由i
找到原始节点、由
在常规复制链表时,会对链表进行一次遍历。在遍历的同时:
- 在
map1
内记录原始节点p
对应的原始random
节点; - 在
map2
内记录原始节点p
对应的下标i
; - 在哈希表
map3
记录下标i
对应的原始节点p
; - 在哈希表
map4
记录下标i
对应的新节点p
。
区分map3
和map4
的原因是链表节点指向的是数据域
(某个地址) 的特性。
当常规复制完成后,再回到新的链表头复制random
部分。
对于第i
个新节点p
,由哈希表map3
找到原链表中对应的第i
个原始节点map3[i]
,再通过map1
找到其对应的原始random
节点。
如果原始random
节点不指向null
的话,则先由map2
找到原始random
节点对应的下标i
,
再由map4
找到其对应的新的random
节点map4[i]
,就是新节点p
的random
指针应该指向的地方。
我的代码
1 | class MySolutionOffer35 { |
方法一:回溯 + 哈希表
思路:利用回溯的方式,让每个节点的拷贝操作相互独立。
对于当前节点,首先对其进行拷贝, 然后进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝, 拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。
具体地,用哈希表记录每一个节点对应新节点的创建情况。 遍历时,检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。 如果这两个节点中的任何一个节点的新节点没有被创建,都立刻递归地进行创建。 当拷贝完成,回溯到当前层时,即可完成当前节点的指针赋值。
注意一个节点可能被多个其他节点指向,因此可能递归地多次尝试拷贝某个节点。 为了防止重复拷贝,需要首先检查当前节点是否被拷贝过。 如果已经拷贝过,可以直接从哈希表中取出拷贝后的节点的指针并返回即可。
在实际代码中,我们需要特别判断给定节点为空节点的情况。
1 | class Solution1 { |
时间复杂度: \(O(N)\). 对于每个节点,我们至多访问其「后继节点」和「随机指针指向的节点」各一次,均摊每个点至多被访问两次。
空间复杂度: \(O(N)\). 哈希表的空间开销。
方法二:迭代 + 节点拆分
思路:希望省去方法一当中哈希表所消耗的空间。
首先,将该链表中每一个节点拆分为两个相连的节点。 例如,对于链表\(A→B→C\), 我们可以将其拆分为\(A→A'→B→B'→C→C'\)。 即,对于任意一个原节点\(S\),其拷贝节点 \(S'\) 即为其后继节点。
这样,我们可以直接找到每一个拷贝节点 \(S'\) 的随机指针应当指向的节点, 即为其原节点 \(S\) 的随机指针指向的节点 \(T\) 的后继节点 \(T'\) 。
需要注意原节点的随机指针可能为空。
当完成了拷贝节点 \(S'\) 的随机指针 \(T'\) 的赋值, 就只需要将这个链表按照原节点 \(S\) 与拷贝节点 \(S'\) 的种类进行拆分即可,只需要遍历一次。
同样需要注意最后一个拷贝节点的后继节点为空的情况。
1 | class Solution2 { |
时间复杂度:\(O(N)\). 只需要遍历三次。(如果在计算拷贝节点的随机指针的同时计算其后继指针,则只需要遍历两次。)
空间复杂度:\(O(1)\). 注意返回值不计入空间复杂度的计算。