目录
ID:HL_5461
一、题目
题目链接:
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目分析:
如果只是单纯的复制链表其实没多难,难点在于复制的链表节点带有随机指针random,而且复制链表中的指针都不应指向原链表中的节点 。这需要我们需要清楚的知道每个原节点所对应的复制节点的位置。知道位置我们挨个遍历每个复制节点然后令它们的random指向相应的位置就可以了。
二、思路
方法一:
该方法 时间复杂度O(N^2),空间复杂度O(1)
将原链表视作普通单链表进行复制这样我们就拥有两条链表啦~当然,我一般喜欢复制时加一个哨兵位的头结点,不加也可以,但要加一步判断是否为头结点。总之,个人喜好吧,加了……嗯,很香!
创建四个指针变量:cur和curcopy分别遍历两个链表,find1和find2用于找random所在位置。四个指针初始位置分别为所在链表的第一个结点。
(别找不同了,这还是上面那张图)
此时,cur->random为NULL,那么也令curcopy的random为NULL。修改完后令cur和curcopy都移向对应的next节点。同时注意将find1和find2都指回对应链表的第一个结点(因为这里find1和find2没移动,所以指回头结点在我们看来就跟没操作一样)。
此时cur->next != NULL,我们开始移动find1,令find1去找random,find1每移一步,find2也移动一步。
(因上一幅图cur->random为第一个结点,看不出两个find指针的移动效果,我们这里假设前面已经处理完成,此时cur指向如图第三个结点,然后移动find1指针,并移动find2指针)
直到find1 == cur->random,此时find2的位置为复制链表对应的random位置。令curcopy->random = find2。
如图,当cur == NULL时循环结束。因为是一个指针cur遍历列表的循环中嵌套了一个find1找random的循环,所以该方法的时间复杂度为O(N^2),没有开辟额外空间(复制开辟的新链表不算额外空间),空间复杂度为O(1)。(时间复杂度看不懂没关系,后面看代码就清楚了)。最后,free哨兵位节点,返回哨兵位头节点的后一位,即真正的第一个结点。
方法二:
该方法 时间复杂度O(N),空间复杂度O(1)
这个方法真的很妙,但是真的不容易想也不容易理解。我们分为三部分说明。
1.复制节点
使用malloc开辟新节点,修改新结点的next指针和cur的next指针,使得新结点能插入原链表。
next和cur均向后移(cur = next,next = cur->next),然后重复上面插入结点的步骤。
直到每个原结点都拥有了在自己后面的copy节点,此时循环停止。由此循环终止的条件为cur == NULL。估计有人会纠结next怎么办,因为next是cur在原链表的下一个嘛,此时cur为NULL,也就说next = NULL->next,显然是有问题的。但咱不妨换个思路,把next = cur->next这条语句放到循环的第一句,也就说每次进循环先执行这条语句。当cur为NULL时根本不会进循环,也就不会出现我们纠结的问题了。
2.设置random
设置两个指针,cur遍历原链表,copy遍历拷贝链表。
如果cur->random为空,将copy->random也置为空。cur、copy指针后移。
如果cur->random不为空,令copy->random等于cur->random->next。cur、copy指针后移。
直至cur == NULL循环结束。
3.分离链表
我们依旧先设置一个哨兵位的头节点,然后使用三个指针:cur记录copy的前一个节点,curnext记录copy的后一个节点,它们用于恢复原链表, copy遍历拷贝链表,将拷贝链表从原链表上分离下来在copyhead链表上进行尾插。同时,为了方便尾插,设置一个tail指针记录copyhead的尾结点。
将copy结点节点尾插到copyhead链表上,tail指针后移,指向新的尾节点,养成好习惯,将tail的next指针置空。cur的next指向curnext节点。(为了避免图太凌乱,我这里省略了拷贝链表上的random指针的线)
重复操作,直至cur为空。
我们把之前省略的random线补上,这个是最终的效果~当然,和方法一一样,我们返回哨兵位头节点的后一位,也别忘了free哨兵位节点。
三、代码
我们在这两个代码的开头都加上if的判断,如果头指针为空,直接返回空指针。
方法一:
//方法一:
struct Node* copyRandomList(struct Node* head)
{
if (head == NULL)//链表为空
{
return NULL;//直接返回空指针
}
//1.复制链表
struct Node* copyhead = (struct Node*)malloc(sizeof(struct Node));//为哨兵位头节点开辟空间
struct Node* cur = head;//cur指针遍历原链表
struct Node* tail = copyhead;//尾指针记录copyhead链表的尾节点,方便尾插
while (cur)
{
struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));//为复制节点开辟空间
newnode->val = cur->val;//新节点val与cur的val相同
newnode->next = NULL;//新节点的next指针指向空(一点好习惯)
tail->next = newnode;//新结点尾插到copyhead链表上
tail = tail->next;//tail指向新的尾节点
cur = cur->next;//cur指针后移
}
//2.设置random
cur = head;//cur遍历原链表
struct Node* curcopy = copyhead->next;//curcopy遍历复制链表(从第一个结点开始)
struct Node* find1 = head;//find1找寻原链表random
struct Node* find2 = copyhead->next;//find2确定复制链表random
for (; cur; cur = cur->next, curcopy = curcopy->next)//当cur不为空,遍历原链表
{
if (cur->random == NULL)//当cur的random为空
{
curcopy->random = NULL;//将对应的curcopy也置为空
}
else//当cur的random不为空
{
for (find1 = head, find2 = copyhead->next; find1 != cur->random; find1 = find1->next, find2 = find2->next)//遍历找寻cur的random
{
;
}
curcopy->random = find2;//curcopy的random为此时的find2
}
}
//3.返回
struct Node* first = copyhead->next;//记录第一个节点
free(copyhead);//释放哨兵位头节点
return first;//返回第一个结点地址
}
方法二:
//方法二:
struct Node* copyRandomList(struct Node* head)
{
if(head == NULL)//链表为空
{
return NULL;//直接返回空指针
}
//1.复制节点
struct Node* cur = head;//cur指针记录当前节点
while(cur)
{
struct Node* next = cur->next;//next指针记录原链表后一个节点
struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));//开辟新节点
newnode->val = cur->val;//令新节点val等于当前节点
//新结点在cur与next之间插入
newnode->next = next;//
cur->next = newnode;
cur = next;//cur指针后移
}
//2.设置random
cur = head;//cur遍历原链表
while(cur)
{
struct Node* copy = cur->next;//copy遍历拷贝链表
if(cur->random == NULL)//当前原链表节点random为空
{
copy->random = NULL;//将拷贝链表当前节点也置空
}
else//当前原链表节点random不为空
{
copy->random = cur->random->next;//拷贝链表当前节点的random为原链表当前节点random的后一个
}
cur = cur->next->next;//cur在原链表节点上后移一位
}
//3.分离链表
struct Node* copyhead = (struct Node*)malloc(sizeof(struct Node));//开辟哨兵位头结点
cur = head;//cur遍历原链表
struct Node* tail = copyhead;//尾指针初始指向copyhead哨兵位的头结点
while(cur)
{
struct Node* copy = cur->next;//copy遍历拷贝链表
struct Node* curnext = copy->next;//curnext记录原链表的下一个方便还原原链表
tail->next = copy;//取拷贝链表节点尾插
copy->next = NULL;//尾结点置空
cur->next = curnext;//还原原链表
cur = curnext;//后移cur指针
tail = tail->next;//后移tail指针
}
struct Node* newhead = copyhead->next;//记录拷贝链表的第一个节点
free(copyhead);//释放哨兵位头结点
return newhead;//返回拷贝链表
}
结尾
这题个人认为一个是思想难,方法二着实不容易想到,另一方面需要对链表的插入很熟练,这题可以算得上是链表基本操作的一个总结吧。
最后,若有错误,欢迎大家批评斧正!