剑指offer 35 - 复杂链表的复制 - python

91 阅读3分钟

题目描述:

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:


在这里插入图片描述

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2


在这里插入图片描述

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3


在这里插入图片描述

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  • − 10000 < = N o d e . v a l < = 10000 -10000 <= Node.val <= 10000 −10000<=Node.val<=10000
  • Node.random 为空(null)或指向链表中的节点
  • 节点数目不超过 1000

复制单纯的单链表很容易,根据题目描述主要需要考虑每个结点的随机指针,即每个结点random的指向。

  • 暴力求解:先遍历列表同时记录val和random域的值,分别存放在val_listrandom_list中,然后,首先使用val_list中的元素依次新建结点并设置next构成基本链。构建两个指针pr,其中p指向当前结点,r用于搜索random指向的结点。接着从新链第一个节点newHead.next开始,看它在random_list中是否有值,如果有,从头遍历链表,直到找到val为指定值的结点,将当前结点的random指向它,同时将p指向下一个位置,r回到头结点。不断遍历,直到p.next == None为止。但这样的算法复杂度为O(n^2),在OJ平台上会发生时间超出的错误。

  • 聪明的求法:先依次复制给定链表的结点构成新链,然后再断链得到原始链表的复制,具体的过程如下所示:


    在这里插入图片描述

​假设初始链表如上图所示,链表中包含1、2、3、4四个结点,结点的random指向分别为:

1.random = 3
2.random = 2 
3.random = 4 
4.random = 2

​首先,复制每个结点将其链接在它后面构成如(2)所示的新链:

cur = head
# 始终保证cur指向原始链表中的结点
while cur:
    node = Node(cur.label)
    # 在原始链中插入
    node.next = cur.next
    cur.next = node
    cur = node.next

得到插入复制结点后的新链表后,我们还需要设置新结点的random,使其和它和给定链表中的结点一致:

cur = head
while cur:
    # 可能某些结点的random为None,因此先进行判断
    if cur.random:
        cur.next.random = cur.random.next
        
     # 始终保证cur指向原始链表中的结点
     cur = cur.next.next

​经过上述步骤,新链表的中每个结点的nextrandom都指向了正确的结点,但这样的链表不是我们想要的。由于不能直接返回给定链表的头结点,我们需要从新链表中将复制的节点抽出来,同时保证抽出的结点的next和random指向和最开始的链表一致。


在这里插入图片描述

cur = head
# 指向复制结点中的第一个结点,只用于输出
h = head.next
# 指向复制结点中的第一个结点,用于断链
p = head.next

while cur:
    # 断开原链表中的结点和复制结点的next指向,将next指向原链表中的对应结点,即next.next
    cur.next = cur.next.next
    # 看是否遍历完了所有的结点
    if p.next:
        # 类似上一步
        p.next = p.next.next
        # 由于此时new_cur指向的结点的next已经发生了改变,因此只需要将next后移即可
        p= p.next
        
    # 类似new_cur的上一步操作
    cur = cur.next

最终newHead作为头结点的链表就是复制后的链表,即我们想要的结果。由于在复制完结点后所做的只是结点next和random指向的操作,因此时间复杂度为 O ( n ) O(n) O(n)。

"""
# 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 copyRandomList(self, head: 'Node') -> 'Node':
        if not head: return None

        cur = head
        # next
        while cur:
            node = Node(cur.val)
            node.next= cur.next
            cur.next = node
            cur = cur.next.next

        # random
        cur = head
        while cur:
            if cur.random:
                cur.next.random = cur.random.next
            cur = cur.next.next
        
        # 
        cur = head
        h = head.next
        p = head.next
        while cur:
            cur.next = cur.next.next
            if p.next:
                p.next = p.next.next
                p = p.next
            cur = cur.next


        return h