目录
前言
通过对链表的学习,我们了解到了链表的基本特性和实现方法。而实际上,链表是一个工具。在做题的时候能灵活运用链表的知识才是我们学习链表的最终目的。今天我们通过链表的知识,来实现一道难度较高的链表应用题目。
一、题目
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
下图为三个复杂链表的样例,题目的目的就是复制出这样的一个链表。
二、方法一(暴力求解)
最平常的思路我们会想到,先开辟一条和复杂链表的数值以及next指针域都一样的单链表。(如下图)
然后是设置random指针域,但是由于单链表只能向后寻找,不能向前寻找,所以,在确定random指针的时候,需要遍历链表才能实现。
这里需要设施一个变量i来记录每个结点的相对位置,而不能单单通过比较每个结点的数值就确定random域的指向,因为结点的值有可能相同,单纯的比较数值可能会出现问题。
在最坏的情况下这种情况每个结点都要把整个链表完全遍历一遍,时间复杂度为O(n^2),效率较低,所以不推荐这种暴力求解的方法。
三、方法二
1.思路分析
1.第一步,拷贝结点链接在源结点后面(如下图)
2.第二步,拷贝随机结点random,这里假设cur指向原结点,而copy指向这个结点的拷贝结点。由此可以总结出来一个公式,cur->random指向非空指针,copy->random=cur->random->next(这个是这个方法的核心公式);如果cur->random指向空指针直接给copy->random赋值为空。通过这个公式就能让整个方法只需要遍历链表一次,时间复杂度O(n),效率大大提升。
3.第三步,把拷贝结点解下来,让他成为一掉单独的链表,实现方法就是单链表的尾插。最后恢复原链表。
2.代码逐步实现
复杂链表的结构体:
struct Node {
int val;
struct Node* next;
struct Node* random;
};
第一步:链接源链表
struct Node* cur = head;
//把两个链表链接在一起,新结点放在旧结点之后
while (cur)
{
struct Node* next = cur->next;
//逐个开辟,并且赋值
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
//链接结点
cur->next = copy;
copy->next = next;
cur = next;
}
第二步:填充random指针域
//random指针的填充
cur = head;
while (cur)
{
struct Node* copy = cur->next;
if (cur->random == NULL)
{
copy->random = NULL;
}
else
{
//关键公式
copy->random = cur->random->next;
}
cur = copy->next;
}
第三步:解下拷贝链表
//拉出来变成一条独立的链表
cur = head;
//新链表的头尾指针
struct Node* linkhead = NULL;
struct Node* linktail = NULL;
while (cur)
{
struct Node* copy = cur->next;
//尾插
if (linkhead == NULL)
{
linkhead = linktail = copy;
}
else
{
linktail->next = copy;
linktail = linktail->next;
}
//链接恢复原链表
cur->next = copy->next;
cur = cur->next;
}
3.完整代码
struct Node* copyRandomList(struct Node* head) {
struct Node* cur = head;
//把两个链表链接在一起,新结点放在旧结点之后
while (cur)
{
struct Node* next = cur->next;
//逐个开辟,并且赋值
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
//链接结点
cur->next = copy;
copy->next = next;
cur = next;
}
//random指针的填充
cur = head;
while (cur)
{
struct Node* copy = cur->next;
if (cur->random == NULL)
{
copy->random = NULL;
}
else
{
//关键公式
copy->random = cur->random->next;
}
cur = copy->next;
}
//拉出来变成一条独立的链表
cur = head;
//新链表的头尾指针
struct Node* linkhead = NULL;
struct Node* linktail = NULL;
while (cur)
{
struct Node* copy = cur->next;
//尾插
if (linkhead == NULL)
{
linkhead = linktail = copy;
}
else
{
linktail->next = copy;
linktail = linktail->next;
}
//链接恢复原链表
cur->next = copy->next;
cur = cur->next;
}
return linkhead;
}
四、总结
于一般的链表题目相比,这道题目的难度会比较高,在力扣上也给到了中等的难度。首先,此题最优解法的思路是比较难想到的,这点就是为什么我们要多刷题的原因。其次,即使思虑明确之后,一些同学对他的实现还依旧存在困难,因为这道题对代码能力的要求也不低,融汇了链表中的好几个方面的知识点。