面试经典150题——随机链表的复制

本文探讨了如何深度拷贝一个特殊链表,其中包含一个random指针。作者提供了四种解决方案:思路一是复制链表并逐个查找random指向,思路二是使用哈希表建立原新节点映射,思路三是回溯法结合哈希表,思路四是迭代拆分链表。每种方法都分析了时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

landscape photography of lake and mountain

前两天断更了两天有点事情🤗

1. 题目描述

image-20240310093647464

2.  题目分析与解析

2.1 思路一

开始还是没什么思路,没思路那就先把题目解决不管方法的好坏。如果不考虑复杂度,该怎么解决?

可以有这样的一种思路:

  1. 首先复制链表的所有节点,创造一个除了random全为空的相同链表

  2. 接下来按照每一个节点的random需要指向的位置,一个一个找到random指向的值,对random赋值

代码思路如下:

  1. 创建一个新的链表作为结果,一个指针指向结果链表的头部

  2. 遍历参数链表,复制一个一摸一样的链表

  3. 遍历参数链表,复制随机指针

  4. 返回结果链表

没想到还没有报超时:

image-20240314172831091

2.2 思路二——采用hash表

因为题目中只有一个random节点是格外需要注意的,那么我们就可以针对random节点怎么样想一种办法,根据思路一,让复制的链表在找random时能够最快的找到

因为如果要在找random时能够最快的找到,那么肯定是找引用,因为我们找的是一个Node对象。而结果链表和原始链表也肯定是对称的,也就是说原始链表的某一个节点的random指向某个节点,那么结果链表的对应节点的random也会指向对应的节点。所以难点就在于怎么样找到对应关系。

因此我们就可以想到定义一个<Node, Node>类型的结构,键作为原始链表的节点,值作为结果链表的节点。

先把所有的键都设置为原始链表的所有节点,那么键是不是也相当于构成了一个链表,对应的,我们再去对结果链表进行赋值。

  • 结果链表的某一个节点的next是不是就相当于原始链表的对应节点,也就是的next指向的的值?

  • 而random节点不也是对应的的random指向的的值?

所以我们可以有如下代码思路:

  1. 先创建一个哈希表,捆绑原始节点和新节点

  2. 遍历原始链表,更新新链表的next和random指针

  3. 返回新链表的头部

2.3 思路三——回溯 + 哈希表

引自作者:力扣官方题解

本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。

一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。

具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。

注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。

在实际代码中,我们需要特别判断给定节点为空节点的情况。

具体见后代码实现注释。其实这种思路和思路二是有点相似的,根据hashMap中的值来连接。

3.4 思路四——迭代 + 节点拆分

引自作者:力扣官方题解——我在代码中加了详细注释,看不懂题解可以直接看代码和动图

image-20240314210118271

recording

3. 代码实现

3.1 思路一

image-20240314173043930

image-20240314201440711

3.2 思路二

image-20240314201422107

image-20240314202232781

3.3 思路三

image-20240314205434543

image-20240314205344428

3.4 思路四

image-20240314211346947

image-20240314211357741

4. 相关复杂度分析

思路1

  • 时间复杂度: O(n²)。这种方法涉及到两次遍历原始链表。在复制随机指针时,对于每个节点都需要从头遍历链表以找到随机指向的节点,因此最坏情况下的时间复杂度为O(n²)。

  • 空间复杂度: O(1)。该方法仅使用了常数级别的额外空间(不考虑输出的空间),主要是几个临时变量。

思路2

  • 时间复杂度: O(n)。这种方法只需要遍历链表两次,一次用于复制节点并建立原节点和新节点之间的映射,另一次用于更新新链表中各节点的next和random指针。每次操作都可以在常数时间内完成,因此时间复杂度为O(n)。

  • 空间复杂度: O(n)。该方法需要一个HashMap来存储原节点与新节点之间的映射,最坏情况下需要存储n个键值对,因此空间复杂度为O(n)。

思路3

  • 时间复杂度: O(n)。通过递归的方式复制每个节点及其next和random指针。由于使用了哈希表来避免重复复制节点,确保每个节点只被复制一次,所以总的时间复杂度为O(n)。

  • 空间复杂度: O(n)。除了递归调用栈外,该方法同样需要一个HashMap来存储已经复制过的节点,以避免重复复制。最坏情况下,HashMap和递归栈的空间复杂度均为O(n)。

思路4

  • 时间复杂度: O(n)。该方法涉及三次遍历链表:一次用于在原节点后插入复制节点,一次用于更新所有新节点的random指针,最后一次用于拆分链表。每次遍历都可以在O(n)时间内完成。

  • 空间复杂度: O(1)。此方法在原始链表的基础上直接操作,不需要使用额外的空间来存储节点映射(忽略输出链表所占用的空间)。只使用了常数级别的额外空间,主要是几个用于遍历链表的临时变量。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值