LeetCode 138:复制带随机指针的副本
2023-11-16 19:04:54
复制带随机指针的链表:深入剖析 LeetCode 138
导言
在软件开发中,处理具有复杂结构的数据结构是不可避免的。其中一个具有挑战性的数据结构是带有随机指针的链表。它与传统的链表不同,其中每个节点有一个指向下一个节点的 next 指针,而是每个节点还有一个指向链表中任意节点(或空节点)的 random 指针。复制这样的链表是一个棘手的问题,在 LeetCode 138 难题中得到了体现。本博客将深入探讨解决此难题的两种有效方法,并通过代码示例进行详细说明。
方法 1:哈希表
原理
此方法利用哈希表来存储已复制节点的映射关系。我们遍历原始链表,将每个节点作为键,其复制节点作为值存储在哈希表中。然后,我们再次遍历原始链表,更新每个节点的 next 和 random 指针。对于 next 指针,我们在哈希表中查找复制节点的 next 指针。对于 random 指针,我们在哈希表中查找复制节点的 random 指针。这种方法的优点是它易于实现且时间复杂度为 O(n),其中 n 是链表的长度。
代码示例
def copyRandomList(head):
# 创建一个哈希表
node_map = {}
# 遍历原始链表,复制每个节点
curr = head
while curr:
node = RandomListNode(curr.val)
node_map[curr] = node
curr = curr.next
# 更新 next 和 random 指针
curr = head
while curr:
node = node_map[curr]
node.next = node_map.get(curr.next)
node.random = node_map.get(curr.random)
curr = curr.next
# 返回复制的链表
return node_map[head]
方法 2:拆分链表
原理
此方法通过拆分原始链表来创建复制的链表。我们遍历原始链表,并在每个节点后面插入一个复制的节点。然后,我们更新所有复制节点的 next 指针,使其指向原始节点的 next 指针。接下来,我们更新所有复制节点的 random 指针,使其指向原始节点的 random 指针。最后,我们将复制的链表与原始链表拆分,返回复制的链表头节点。这种方法的时间复杂度也为 O(n),但空间复杂度为 O(1),因为我们不需要额外的哈希表。
代码示例
def copyRandomList(head):
# 复制每个节点
curr = head
while curr:
next = curr.next
copy = RandomListNode(curr.val)
curr.next = copy
copy.next = next
curr = next
# 更新 next 和 random 指针
curr = head
while curr:
if curr.next:
curr.next.next = curr.random.next
curr = curr.next.next
# 拆分链表
copy = head.next
curr = head
while curr:
next = curr.next.next
curr.next = next
curr = next
return copy
结论
复制带有随机指针的链表是一个常见的数据结构挑战,在 LeetCode 138 难题中得到了体现。使用哈希表或拆分链表的方法都可以有效地解决此问题,每种方法都有其优点和缺点。了解这两种方法至关重要,以便选择最适合特定用例的方法。
常见问题解答
-
哪种方法更有效率?
对于较小的链表,哈希表方法可能更有效率,因为它避免了额外空间的使用。但是,对于较大的链表,拆分链表方法可能更有效率,因为它避免了哈希表查找的开销。 -
哪种方法的空间复杂度更低?
拆分链表方法的空间复杂度为 O(1),而哈希表方法的空间复杂度为 O(n),因为需要额外的空间来存储哈希表。 -
如何处理环形链表?
这两种方法都不能处理环形链表,因为它们都会导致无限循环。 -
是否有通用的方法来处理所有类型的链表?
不,没有一个通用的方法可以处理所有类型的链表。对于具有复杂结构的链表(例如交叉链表),可能需要使用不同的方法。 -
何时应该考虑使用哈希表或拆分链表方法?
哈希表方法适用于较小或中等大小的链表,而拆分链表方法适用于较大或具有复杂结构的链表。