目录
题目描述
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
- val:一个表示 Node.val 的整数。
- random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
思路:
这个题目的难点在于 , random 指针的复制,比如上述的例子,第二个结点 " 13 " 的 random 指针 " 7 " 这个结点,那么我们在新的链表空间中,如果和去找到这个 原链表 " 7 " 对于新链表的 " 7 " 这个位置。
我们不能按照 值 去查找,比如上述中 我们想要找到 值为 " 7 " 这个结点,因为 链表中可能有多个 值为 " 7 " 的结点。如果按照地址去查询也是不行的,因为我们只能通过循环 在原链表中找到对应结点的地址,但是我们是在新的链表中进行random指针的查找,新的链表和 原链表地址是不同的。
如果我们想要按照 地址去查找,就只能通过相对地址来寻找,但是很麻烦,简单俩说就是,如上例子,我们想要找到 13 的random 指针位置,13 的random 指针指向的位置是 13 这个结点的 -1 的相对位置; 11 这个结点的 random指针指向位置就是 1 这个结点,1 这个结点 的相对位置就是 11 的 2 的这个相对位置。
或者说是,找到 在新链表中 位于第几个 结点,虽然地址 ,新链表和 原链表对不上,但是从起始位置开始数的个数,是对应上的。用这种方法,时间复杂度是 O(n^2)。
或者我们可以创建两个指针数组,一个按照链表顺序存储 原链表 各个结点的地址,一个存储 新链表结点地址,然后我们在 原链表中 找到某一个结点的random 所在 链表的位置,这个位置也就对应这个数组的下标,那么我们可以在 存储 新链表结点 的数组 同一下标位置找到 新链表当中 这个结点 的 random 指针链表当中的位置,如下图所示:
下标是 可以视为 这个结点在链表当中的相对位置。但是这个方式只是比之前快一点点,不能优化到 O(n)。
更优解是:
我们在原链表的每一个结点后,都创建一个新的结点,这个结点和前一个结点的值相同,如下图所示(拷贝结点链接到源节点的后面):
那么我们之前的难点是在,我们在 新链表的当中找不到 结点的 random 指向的地址,那么现在我们就可以找到了,如上述,红色的 结点就是我们的 新链表的结点,红色的 13 这个结点的 random指针 就应该 红色 7 这个结点,而 7 这个结点的位置就在 原链表 7 这个结点的后面,那么同样的,其他的结点也是一样的,都是在 原链表 random 指针指向结点的后一个结点。
然后就可以通过上述关系来把红色结点,也就是新的链表结点 的 random 指针的进行修改。
最后就是 把 拷贝结点解下来连接成一个新的链表,把原链表恢复。
具体代码实现:
struct Node* copyRandomList(struct Node* head) {
struct Node* cur = head;
while(cur)
{
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
//修改链表当中的链接关系
struct Node* tmp = cur->next;
cur->next = copy;
copy->next = tmp;
// cur 往后迭代
cur = tmp;
}
// 处理 结点的 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;
}
// 把 拷贝结点链接为新的链表,把原链表恢复
struct Node* copyhead = NULL, *copyTail = NULL;
cur = head;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
// copy 尾插
if(copyhead == NULL)
{
copyhead = copyTail = copy;
}
else
{
copyTail->next = copy;
copyTail = copyTail->next;
}
// 恢复原链表
cur->next = next;
// cur 跌倒往下走
cur = next;
}
return copyhead;
}