前两天断更了两天有点事情🤗
1. 题目描述
2. 题目分析与解析
2.1 思路一
开始还是没什么思路,没思路那就先把题目解决不管方法的好坏。如果不考虑复杂度,该怎么解决?
可以有这样的一种思路:
-
首先复制链表的所有节点,创造一个除了random全为空的相同链表
-
接下来按照每一个节点的random需要指向的位置,一个一个找到random指向的值,对random赋值
代码思路如下:
-
创建一个新的链表作为结果,一个指针指向结果链表的头部
-
遍历参数链表,复制一个一摸一样的链表
-
遍历参数链表,复制随机指针
-
返回结果链表
没想到还没有报超时:
2.2 思路二——采用hash表
因为题目中只有一个random节点是格外需要注意的,那么我们就可以针对random节点怎么样想一种办法,根据思路一,让复制的链表在找random时能够最快的找到。
因为如果要在找random时能够最快的找到,那么肯定是找引用,因为我们找的是一个Node对象。而结果链表和原始链表也肯定是对称的,也就是说原始链表的某一个节点的random指向某个节点,那么结果链表的对应节点的random也会指向对应的节点。所以难点就在于怎么样找到对应关系。
因此我们就可以想到定义一个<Node, Node>类型的结构,键作为原始链表的节点,值作为结果链表的节点。
先把所有的键都设置为原始链表的所有节点,那么键是不是也相当于构成了一个链表,对应的,我们再去对结果链表进行赋值。
-
结果链表的某一个节点的next是不是就相当于原始链表的对应节点,也就是键的next指向的键的值?
-
而random节点不也是对应的键的random指向的键的值?
所以我们可以有如下代码思路:
-
先创建一个哈希表,捆绑原始节点和新节点
-
遍历原始链表,更新新链表的next和random指针
-
返回新链表的头部
2.3 思路三——回溯 + 哈希表
引自作者:力扣官方题解
本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。
一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。
具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。
注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。
在实际代码中,我们需要特别判断给定节点为空节点的情况。
具体见后代码实现注释。其实这种思路和思路二是有点相似的,根据hashMap中的值来连接。
3.4 思路四——迭代 + 节点拆分
引自作者:力扣官方题解——我在代码中加了详细注释,看不懂题解可以直接看代码和动图
3. 代码实现
3.1 思路一
3.2 思路二
3.3 思路三
3.4 思路四
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)。此方法在原始链表的基础上直接操作,不需要使用额外的空间来存储节点映射(忽略输出链表所占用的空间)。只使用了常数级别的额外空间,主要是几个用于遍历链表的临时变量。