【LeetCode 单链表专项】复制带随机指针的链表(138)

本文介绍了一种特殊的链表结构,其中每个节点除了包含常规的next指针外,还包含一个指向链表中任意节点或空节点的random指针。文章详细讨论了如何实现这种链表的深拷贝,包括三种不同的解法及其时间空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 题目

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n n n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n n n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, index] 表示:

  • val:一个表示 Node.val 的整数。
  • index:随机指针指向的节点索引(范围从 0 0 0 n − 1 n-1 n1);如果不指向任何节点,则为 null

你的代码 接受原链表的头节点 head 作为传入参数。

1.1 示例

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

在这里插入图片描述

  • 示例 2 2 2
    • 输入: head = [[1, 1], [2, 1]]
    • 输出: [[1, 1], [2, 1]]

img

  • 示例 3 3 3

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

img

  • 示例 4 4 4

    • 输入: head = []
    • 输出: []

1.2 说明

1.3 限制

  • 0 <= n <= 1000
  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。

2. 解法一(迭代+哈希表)

2.1 分析

此题乍一看似乎和其他链表类题目不太一样,其他链表类题目的要求其实都很简单直接,一般只要设置好辅助指针变量,然后在每一次迭代过程中正确地更新辅助指针的指向就可以总结出解法;然而,此题因为每个节点都有 random 指针的缘故,所以似乎很难设置并更新指针变量。

实际上,解答本题并不需要想得太复杂,题目要求我们复制链表那就直接迭代复制即可,因为单单根据节点的 next 指针其实就可以将链表的骨架复制出来了,除此之外我们只需要在复制过程中特别注意因节点 random 指针导致的影响即可。

具体地,在根据 next 指针进行链表复制时,在同时填充每个节点的 random 指针时,由于每个节点的 random 指针都可能指向链表的任何节点或 None ,这会导致两种可能的情况:

  1. 当前节点的 next 指针所希望指向的下一个节点可能在之前已经被创建过了,因为在之前的节点复制过程中,某些节点的 random 指针需要指向该节点;
  2. 当前节点的 random 指针所希望指向的节点更可能在之前也已经被创建过了,因为在之前的节点复制过程中,某些节点的 random 指针或 next 指针都可能需要指向该节点。

针对上述情况,如果不做特殊处理,那么就有可能导致在复制过程中重复创建 val 域相同的节点,而且通过 random 指针建立的链接关系也不会符合预期。

针对上述问题的解决方案也很简单,只要在复制过程中通过一个字典(其他语言一般称为映射)建立原链表和复制链表对应位置节点间的映射即可。

2.2 解答

from typing import Optional


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 copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return head

        copy_map = {None: None}
        copy_head = Node(head.val)
        copy_map[head] = copy_head
        cursor, copy_cursor = head, copy_head

        while cursor:
            if cursor.next not in copy_map.keys():
                copy_cursor.next = Node(cursor.next.val)
                copy_map[cursor.next] = copy_cursor.next
            else:
                copy_cursor.next = copy_map[cursor.next]

            if cursor.random not in copy_map.keys():
                copy_cursor.random = Node(cursor.random.val)
                copy_map[cursor.random] = copy_cursor.random
            else:
                copy_cursor.random = copy_map[cursor.random]

            cursor = cursor.next
            copy_cursor = copy_cursor.next

        return copy_head

需要指出的是,上述解答之所以用 copy_map = {None: None} 的形式来初始化所使用的映射,原因在于所有节点的 random 指针都可能指向 None

2.3 复杂度

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

2.4 代码优化

实际上,上述代码在分别填充当前节点的 next 指针和 random 指针时代码完全相同,仅迭代过程中针对的节点属性不同,下面使用 Python 内置的 getattr()setattr() 方法对其进行了简化:

from typing import Optional


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 copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return head

        copy_map = {None: None}
        copy_head = Node(head.val)
        copy_map[head] = copy_head
        cursor, copy_cursor = head, copy_head

        while cursor:
            pointers = ['next', 'random']
            for pointer in pointers:
                if getattr(cursor, pointer) not in copy_map.keys():
                    val = getattr(getattr(cursor, pointer), 'val')
                    setattr(copy_cursor, pointer, Node(val))
                    copy_map[getattr(cursor, pointer)] = getattr(copy_cursor, pointer)
                else:
                    setattr(copy_cursor, pointer, copy_map[getattr(cursor, pointer)])

            cursor = cursor.next
            copy_cursor = copy_cursor.next

        return copy_head

上述代码还可进一步优化如下:

from typing import Optional


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 copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return head

        copy_map = {None: None}
        copy_head = Node(head.val)
        copy_map[head] = copy_head
        cursor, copy_cursor = head, copy_head

        while cursor:
            pointers = ['next', 'random']
            for pointer in pointers:
                attr = getattr(cursor, pointer)
                if attr not in copy_map.keys():
                    val = getattr(attr, 'val')
                    setattr(copy_cursor, pointer, Node(val))
                    copy_map[attr] = getattr(copy_cursor, pointer)
                else:
                    setattr(copy_cursor, pointer, copy_map[attr])

            cursor = cursor.next
            copy_cursor = copy_cursor.next

        return copy_head

3. 解法二(迭代+时间换空间)

3.1 分析

实际上,也可以不通过额外的映射存储原链表和复制链表对应位置节点的关系来解答该题。具体地,可以通过以下步骤进行实现:

  1. 首先,通过节点的 next 指针修改原链表,使得原链表和复制链表对应位置的节点呈交替状,例如:原链表为 A --> B --> C --> D,则所谓呈交替状的链表为 A --> A' --> B --> B' --> C --> C' --> D --> D'
  2. 其次,为了准确地将复制节点的 random 指针进行正确链接,可以利用上述节点呈交替状的链表结构;
  3. 最后,通过类似【LeetCode 单链表专项】奇偶链表(328)中的方法,实现原链表和复制链表之间节点的分离。

针对上述 3 3 3 个步骤,关于如何分别进行辅助指针变量的设置和更新,下面通过 3 3 3 组示意图进行描述:

  • 复制原链表节点,使节点呈交替状:
    下图的虚线框分别表示了第一次迭代和最后一次迭代之后链表的状态。

在这里插入图片描述

  • 根据呈交替状链表结构,链接 random 指针:
    下面 3 3 3 张图分别表示链接 random 指针的第 1 1 1 次、第 2 2 2 次和最后一次迭代的过程或结果。

1 1 1 次迭代:

在这里插入图片描述

2 2 2 次迭代

在这里插入图片描述
最后一次迭代:

在这里插入图片描述

  • 分离原链表和复制链表节点:
    下面两张图分别表示第 1 1 1 次和最后一次迭代。

1 1 1 次迭代:

在这里插入图片描述
最后一次迭代:

在这里插入图片描述

3.2 解答

from typing import Optional


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 copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return head
        
        cursor = head
        while cursor:
            succ = cursor.next
            cursor.next = Node(cursor.val)
            cursor = cursor.next
            cursor.next = succ
            cursor = succ
            
        odd, even = head, head.next
        while True:
            if odd.random:
                even.random = odd.random.next
            else:
                even.random = None
            odd = even.next
            if not odd:
                break
            else:
                even = odd.next
                
        even_head = head.next
        odd, even = head, even_head
        while True:
            odd.next = even.next
            odd = odd.next
            if not odd:
                break
            even.next = odd.next
            even = even.next
        return even_head

上述 3 3 3while 循环分别表示上述三个步骤,实际上虽然上述解法没有使用额外的哈希表,但是相较于第一种解法多出了两次遍历,因此我们说这是典型的以时间换空间。

3.3 复杂度

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

4. 解法四(使用标准模块)

实际上,如果允许使用 Python 的标准模块 copy ,那么一行代码就可以解决问题,即使用 copy.deepcopy() 函数:

# 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 copy_random_list(self, head: 'Optional[Node]') -> 'Optional[Node]':
        return copy.deepcopy(head)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值