- C++中,引用结构体时,不需要再写"struct", 此举可以简化掉,平时在 C 语言中常用的 typedef 语句。
1 移除链表中等于 val 的结点(两种方法)
方法1遍历链表,然后再原链表中“指定位置删除”
方法 2新建链表,将原链表中不等于 val 的结点都尾插到新结点
- 此操作亦会使原链表跟着新链表改变,因为每个结点都只能有一个指向。而创建“新链表”其实就是创建两个新指针:newhead 和 newtail,让他们随着 pcur 在原链表的遍历,不断尾插,从而得到新的链表。
首先,“创建新链表”,即创建新的头结点和尾结点,然后创建pcur 遍历链表,直到链表为空(链表如果最开始就为空的话,那么就不遍历链表,不进行任何操作,直接返回 newhead)。
在pcur 链表循环内,分为等于 val 和不等于 val 的两种情况
不等于 val时,对新链表“尾插”,同时又分为两种情况,
(1) 对头节点尾插:将 newhead 和 newtail 同时尾插至 pcur
(2)后续节点的尾插:newhead 不动,将 newtail 一直伴随着 pcur 操作。(在头节点插入之后,newhead 和 newtail 都在 head 位置, 所以不需要将 newtail 后移),而现在对 newtail尾插,然后将newtail 后移。
等于 val 时不做任何操作,等待 pcur 循环至下一个节点。
随后跳出循环,此时 newtail ->next 并没有被置空,所以要置空,但是“如果原链表全是 val或者本来就为空,那么新链表将为空,此时不能对空链表解引用,所以 要设置一个 if 语句限制只有 newtail 不为空才能置空”。
//1——删除链表的等于 val 的结点
struct ListNode* removeElements(struct ListNode* head, int val) {
//方法一:创建新链表,将等于 val 的结点一一尾插到新链表中,最后返回新头结点
ListNode* newhead = NULL;
ListNode* newtail = NULL;
ListNode* pcur = head;
//定义 pcur 遍历原链表
while (pcur)
{
//不等于 val 时,尾插新链表
if (pcur->val != val)
{
//分为两种情况:头结点插入和其余
if (newhead == NULL)
{
//头结点时,直接将新结点头尾都指向它
newhead = newtail = pcur;
}
else
{
newtail->next = pcur;
newtail = newtail->next;
}
}
pcur = pcur->next;
}
//如果原链表全是 val或者为空,那么新链表将为空,此时不能对空链表解引用
if(newhead != NULL){
newtail->next = NULL;
}
return newhead;
}
2 反转链表
思路 1:创建新链表,然后将原链表节点不断头插到新链表
较为简单,基本上就是普通的头插操作
思路 2:创建三个指针,改变链表指向
(1)创建三个指针:n1,n2,n3, 初始时其中n1 指向 NULL,n2 指向头结点,n3 指向后一个节点。
ListNode* n1,* n2,* n3;
//连续书写三个指针的申请的时候,都要写星号“*”。
n1 = NULL;n2 = head;n3 = n2->next;
(2)然后随之遍历链表,n2 指向 n1,随后三个指针都往后走一位,直到 n2 为空(n1 走到尾节点)时,所有节点的指向均已被改变,此时的 n1 即是新的链表的头结点。
- 特殊情况:链表为空的情况,此时单独处理“直接返回原链表头结点即可”
//-2——反转链表
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
{
return head;
}
ListNode* n1,* n2,* n3;
n1 = NULL;n2 = head;n3 = n2->next;
while(n2)
{
n2->next = n1;//n2指向改变
n1 = n2;//n1 后移
n2 = n3;//n2 后移
if(n3)//防止结尾n3 走到空,再对空节点解引用
{
n3 = n3->next;//n3 后移
}
}
//返回新的头结点
return n1;
}
3找到并返回链表的中间节点
思路 1:遍历两次链表,第一次找链表总长度,并计算出中间节点位置,第二次遍历直接走到中间节点位置。
思路 2:快慢指针
创建快慢指针,slow 走一步,fast 就走两步,等到 fast 到结尾时,slow 就到中间节点了。
两种情况:
(1 )链表为偶数个,满指针 slow 走到中间时,快指针 fast 刚好走到尾结点,此时 fast->next 为空。
(2 )链表为奇数个,slow 走到中间时,fast 刚好为空。
所以只需要在一个循环内同时限制这两个条件即可。但是循环条件可能会有点迷惑人:如下
while(fast && fast->next)
//此时循环条件要为“且”,代表只要有 fast 或者 fast->next 任意一个为空,就会跳出循环。
//而且不能把 fast->next 放到前头“whlie(fast->next && fast)”,因为不能对空指针解引用,在链表结点数为奇数个时,fast 会先为 NULL,如果此条件放到前头,那么就会对空指针解引用。
{..................
//快慢指针循环
}
(3)最后直接返回 slow 即可
- 此时不用单独处理链表为空的情况,因为链表为空时,fast 也为空,直接就跳过循环了。
//3——找到并返回链表中间节点
struct ListNode* middleNode(struct ListNode* head) {
//创建快慢指针
ListNode* slow = head,* fast = head;
//设置循环条件,遍历链表
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
4 合并两个有序链表
大思路:创建新链表,遍历原链表比较大小,谁小就尾插到新链表内。
怎么说呢,都是老思路,与合并两个有序数组如出一辙。只不过“合并数组”只需要创建三个指针:其中两个指针 n1 ,n2分别在两个数组中遍历并进行比较,而 n3 从第一个数组的末尾开始往前遍历,依次将大的数据放入。
而“合并链表”需要创建除两个“比较指针”外的头结点与尾结点,初始时均置为空,由于链表的特性:只能从前往后遍历。所以我们在进行两个链表的比较时,要将较小的链表依次尾插到新的链表内(从前往后遍历,与上述数组相反),直到其中一个链表遍历完,直接将剩余的另一个链表尾插到新链表即可。(原链表均不为空的情况下,当其中遍历循环结束后,则必然出现“一个链表为空,另一个链表不为空”,此时将不为空的链表直接尾插到 newtail 后就行了,不用再让 newtail 往后遍历了,此时只需要返回 newhead,所以 newtail 走到哪里都无所谓,只要节点能接上就行)。
- 特殊情况:原链表 list1,list2 其中一方或者均为空,需要单独处理,在遍历开始前,如果 list1 为空则返回 list2,反之返回 list1。 这样也可以处理两者均为空的情况,反正两个均为空,返回哪一个都一样。
- 动态空间申请后,没有初始化的话,系统会自动初始化,而在动态内存释放后,地址没有置空的才会是野指针。
//4——合并两个有序链表
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
//在最初时,处理 list1,2 任意一方为空,或者全空的情况
if(list1 == NULL)//list1为空,直接把list2传回,当两者均为空时,传回的依旧为空。
{
return list2;
}
if(list2 == NULL)//同理
{
return list1;
}
//定义新链表
ListNode* newhead = NULL,* newtail = NULL;
//定义两个指针
ListNode* l1 = list1,* l2 = list2;
while(l1 && l2)
{
if(l1->val <= l2->val)
{
//l1 尾插到新链表
if(newhead == NULL)//当首次尾插时,新链表头结点为空
{
newhead = newtail = l1;
l1 = l1->next;
}
else//非头结点插入时
{
newtail->next = l1;
newtail = newtail->next;
l1 = l1->next;
}
}
else
{
//l2 尾插到新链表
if(newhead == NULL)//当首次尾插时,新链表头结点为空
{
newhead = newtail = l2;
l2 = l2->next;
}
else//非头结点插入时
{
newtail->next = l2;
newtail = newtail->next;
l2 = l2->next;
}
}
}
//正常运行遍历后,l1 与 l2 只要任意一方为真,另一方就为空
//直接将不为空的链表继续尾插即可。但是其无法处理最初时链表为空的情况,因为无法对 newtail 空链表解引用。
if(l1)
newtail->next = l1;
if(l2)
newtail->next = l2;
return newhead;
}
代码冗余的改善
如图,在l1,l2 尾插的过程,因为要分为对空链表和非空链表的尾插(对空链表的尾插要考虑头节点),从而显得代码冗余,加上 l1 和 l2 要执行两次重复操作,代码更冗余了。而改善的情况可以是(1)将此重复代码封装成函数,(2)也可以是修改代码逻辑。修改如下:
(1)首先考虑,两个原链表遍历的时候都需要考虑新链表非空和空的情况,导致代码冗余,原本的逻辑是“将本来为空的 newhead 和 newtail 均指向头节点后,再进行正常的尾插”。
(2)现在我们改变逻辑,直接为newhead 和 newtail 申请同一片动态空间, 让 newhead 成为“哨兵位”,然后遍历时,直接让原链表尾插到 newtail 后,并让newtail 后移,这样每一步遍历的操作就都相同了。
(3)当然了,最后尾插完,就不需要 newhead 了,这里的 newhead 并不是真正意义上的“newhead”,它只不过是一个“用来指向新头节点的一个临时节点”,用完之后就要释放掉内存并且置空了。
(4)注意:最后返回的是 newhead->next,而我们要在函数返回之前把 newhead 释放掉呀!?所以要创建个临时变量ret保存一下 newhead ,最后返回 ret->next 。
//4——2 改善上述代码冗余现象
//为 newhead 和 newtail 申请动态空间,让其成为临时节点,以供尾插,去掉头节点单独处理的情况。
struct ListNode* mergeTwoLists2(struct ListNode* list1, struct ListNode* list2) {
//在最初时,处理 list1,2 任意一方为空,或者全空的情况
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
//定义新链表
ListNode* newhead,* newtail;
//为其申请动态空间,让其成为一个临时节点,以供后续插入
newhead = newtail = (ListNode*)malloc(sizeof(ListNode));
//定义两个指针
ListNode* l1 = list1,* l2 = list2;
while(l1 && l2)
{
if(l1->val <= l2->val)
{
newtail->next = l1;
newtail = newtail->next;
l1 = l1->next;
}
else
{
newtail->next = l2;
newtail = newtail->next;
l2 = l2->next;
}
}
//正常运行遍历后,l1 与 l2 只要任意一方为真,另一方就为空
//直接将不为空的链表继续尾插即可。但是其无法处理最初时链表为空的情况,因为无法对 newtail 空链表解引用。
if(l1)
newtail->next = l1;
if(l2)
newtail->next = l2;
//末尾要记得释放空间并置空
//释放之前要先存起来,因为返回值要用
ListNode* ret = newhead->next;
free(newhead);
newhead = NULL;
//最后返回临时节点的下一个指针,即新的头指针
return ret;
}
5 链表的分割
思路 1:创建一个新链表,将原链表遍历两次,第一次找小于 x 的节点,第二次找大于等于 x 的节点,依次放入新链表中
粗略思路:遍历两遍,先找小的,再找大的,分批依次放入新链表,新链表临时节点申请动态空间,直接后续插入,最开始时考虑一下链表为空的情况,最后把新链表临时节点的下一位节点保存一下,然后释放并置空,返回保存的临时节点的下一位节点
思路 2:创建两个新链表:lesshead,morehead。遍历原链表,小于 x 的节点放入 lesshead,大于等于x 的节点放入 morehead,最后将 lesshead 和 morehead 连接起来。
(1)首先创建 lesshead /lesstail和 morehead/lesstail 两个临时链表(需要申请动态空间),然后 pcur遍历原链表,当<x 就尾插到”小链表“,>=就尾插到“大链表”,直到原链表pcur 遍历结束。
(2)将大链表尾插到小链表后://lesstail->next = morehead->next;。此时并没有万事大吉,因为大链表的尾结点并没有置空,如果不置空直接运行就会陷入死循环。
(3)最后将小链表的头节点保存一下,然后将大小链表的头结点均释放并置空。
//5——链表分割
ListNode* partition(ListNode* pHead, int x) {
//创建大小链表,并申请动态空间
ListNode* lesshead,* lesstail;
lesshead = lesstail = (ListNode*)malloc(sizeof(ListNode));
ListNode* morehead,* moretail;
morehead = moretail = (ListNode*)malloc(sizeof(ListNode));
//遍历原链表,分别插入大小链表
ListNode* pcur = pHead;
while(pcur)
{
//插入小链表
if(pcur->val < x)
{
lesstail->next = pcur;
lesstail = lesstail->next;
}
//插入大链表
else
{
moretail->next = pcur;
moretail = moretail->next;
}
pcur = pcur->next;
}
//链表遍历结束后,要将新链表的尾节点的下一位置空
moretail->next = NULL;
//将大小链表连接起来
lesstail->next = morehead->next;
//最后先把新链表的头节点保存下来,然后再释放并置空大小节点的动态空间
ListNode* ret = lesshead->next;
free(lesshead);
lesshead = NULL;
free(morehead);
morehead = NULL;
return ret;
}
6 链表的回文结构
- 回文结构:就是轴对称图形。如回文数字“1221”,回文字符串“abcba”。
- bool 类型的头文件为“#include<stdbool.h>”。
思路 1:将链表中的数据遍历依次存入另一种数据结构“数组”中,这样就可以利用数组的可随机寻址的特性,直接双指针判断回文结构。
//6——链表的回文结构
//方法一:将链表中的数据插入到数组中进行双指针判断回文结构
//但是此方法要申请数组空间,空复较高,为O(n)
bool chkPalindrome(ListNode* A) {
//定义数组,以供后续用来存放数组
int arr[900];//可以初始化,也可以不初始化,因为后续会进行赋值。
//遍历原链表,将链表数据存入数组中
ListNode* pcur = A;
int i = 0;
while (pcur) {
arr[i++] = pcur->val;
pcur = pcur->next;
}
//然后在数组中用"双指针"判断回文结构
int left = 0;
int right = i-1;
while (left < right) {
if(arr[left] != arr[right])
{
return false;
}
left++;
right--;
}
return true;
}
- 但是这种解法,时间复杂度虽然为 O(n),但是申请了 n 个数组的空间,空间复杂度为 O(n),较为暴力和复杂,不推荐。
思路 2:利用快慢指针,找到中间节点位置,然后创建新链表(可申请动态空间),将原链表后半段直接头插到新链表中(逆置链表),然后用两指针遍历两链表进行比较,来判断回文结构。
//方法二:找中间节点,将后半点逆置,然后与原链表对比。最优方案。
bool chkPalindrome2(ListNode* A) {
//1 找到中间节点
ListNode* mid = middleNode(A);
//2 逆置后半段链表
ListNode* newNode = reverseList(mid);
//比较链表
ListNode* l1 = A,* l2 = newNode;
while (l2) {
if(l1->val != l2->val)
{
return false;
}
l1 = l1->next;
l2 = l2->next;
}
return true;
}
- 时间复杂度 O(n),空间复杂度 O(1)。虽然新建指针较多,但是只申请了一个新结点。具体思路(最优思路):
(1)按照上述 题型 3 中思路,找中间节点。
(2)将中间节点后的链表“反转”。快慢指针遍历指针找中间节点时,因为有链表奇与偶的情况,那么要怎么考虑呢,其实只用一种处理方法即可:直接从 slow 开始(包括slow 指针),将后面的节点全部头插到新链表(这样就会实现后半链表的倒置了)。
链表为奇时,恰好被分割成两相等的部分,正好比较。但是链表为偶的时候,会出现中间节点被多插入到了新链表里,其实我们不用管它,因为它在新链表结尾,所以我们可以直接比较 slow前头的 节点;
(3)比较两个链表。新链表创建后,用l1 ,l2遍历两个链表进行比较,而结束条件直接让l1 或者 l2 中任意一个指针走到空就跳出循环://while(l1 == slow)
循环中万一两者不相等,直接返回false。如果全部相等,则跳出循环,最后返回 true即可。
思路 3:把整个原链表直接逆置到新的链表里,然后直接进行比较两个链表。较为简单,并且也符合时间和空间复杂度。
- 但是!你以为就这么简单吗?其实这里的逆置链表如果用头插的话,那么在新的链表插入之后,原链表就会消失,这还怎么比较呢!?所以还要给每个新节点都申请新的动态空间,这样的话,空间复杂度又成 O(n)了。
7——返回链表的倒数第 k 个节点中的数据
- 很简单:先逆置,然后返回第二个节点的数据。
//7——返回倒数第 k 个节点中的数值
int kthToLast(struct ListNode* head, int k) {
//空链表直接传空
if(head == NULL)
return 0;
//将链表逆置一下
ListNode* newNode = reverseList(head);
//直接找第二个节点
//定义 pcur 等于新链表的头结点
ListNode* pcur = newNode;
for(int i = 1;i < k;i++)
{
pcur = pcur->next;
}
return pcur->val;
}
8——相交链表
具体思路:
- 判断两个链表是否相交的一个思路:遍历两个链表,若两个链表的尾节点相同,则两者一定为“相交链表”。
- 找到相交节点:
情况一:两个链表结点数量相同(如下补齐后的图),定义两个指针遍历比较两个链表,即可找到相交节点。
情况二:结点数数量不相同(如原图),此时就要想办法让其结点数相同,从而才能进行比较,
(1)分别定义两个指针,遍历两个链表,算出两链表节点数量的差值gap
:利用 abs 函数计算差值gap的绝对值。(为了确定谁是长链表)先假设其中一个链表为长链表,并定义长链表指针 longlist,指向假设的长链表,定义短链表指针 shortlist 指向短链表,利用若 if(longlist < shortlist),则把长短链表指针调换一下。
- int abs(int x)函数:计算括号后的绝对值
(2)让节点数多的那一个链表,先走“差值gap”个节点
:利用 while(gap--), 将长链表走 gap 个节点,从而让长链表与短链表一样长。当然如果本来就一样长,那么这个语句就会被跳过。
(3) 然后再进行两个链表同步遍历,进行比较,从而找到相交节点。
:既然两个链表已经同长了,那么怎么判断两链表是否为相交链表呢,其实直接用 while 遍历比较两链表即可,如果有相同节点,则直接返回该结点,若没有则等到遍历结束,返回 NULL。
//8——相交链表
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//1.遍历两个链表,找两链表差值gap
int sizeA = 0,sizeB = 0;
ListNode* l1 = headA,* l2 = headB;
while(l1)
{
sizeA++;
l1 = l1->next;
}
while(l2)
{
sizeB++;
l2 = l2->next;
}
int gap = abs(sizeA - sizeB);
//2.长链表先走 gap 个节点,使两链表同长
//找到长节点
ListNode* longlist = headA;
ListNode* shortlist = headB;
if(sizeA < sizeB)
{
longlist = headB;
shortlist = headA;
}
//让长节点走 gap 个节点
while(gap--)
{
longlist = longlist->next;
}
//3.同步遍历比较两链表
while(longlist && shortlist)
{
if(longlist == shortlist)
{
return longlist;
}
longlist = longlist->next;
shortlist = shortlist->next;
}return NULL;
}
9——环状链表
环状链表:尾节点的 next 指针不为空即为带环链表,尾节点也可以指向自己。
思路:快慢指针:慢指针走一步,快指针走两步,若二者相遇,则为带环链表,若有一方为 NULL,则为普通链表。
//9——环状链表
//较为简单,重在理解快慢指针为何会相遇,快指针走2,慢指针走1
bool hasCycle(struct ListNode *head) {
//直接定义快慢指针
ListNode* fast = head,* slow = head;
//不断遍历,直至相遇,返回 true,若 fast 或者 fast->next 为空,则跳出循环
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
return true;
}
}
return false;
}
- 为什么进入链表的“环”后,快慢指针一定会相遇呢?
假设进入环后,快慢指针的距离为 N,那么慢指针每走一步,快指针就走两步,在不断的循环中,快指针一直在一步一步的追慢指针,而他们又都在环中无法逃离,所以没循环一遍,二者的距离就会减一,于是 N-1,N-2,N-3....... 直到距离为0,二者相遇。
思考:
- 如果慢指针每走一步,快指针走3,4,5.....n 步,那么二者还会相遇吗?
一定会相遇!!!:
设二者的距离为 N,慢指针走一步,快指针走三步,那么二者的距离就会不断缩小 2 个,就是让 N 不断减 2,距离N-2,N-4....到 N-2i(i 为整数),此时会有两种情况,
(1)N 为偶数,则两指针会恰好相遇。
(2)N 为奇数,则第一圈时二者的最近距离会变成“-1”,即快指针会在最接近慢指针的时候,走到了慢指针的后面一个节点,因此第一圈会出现“擦肩而过”的现象,那么第二圈一定会相遇吗?也是分为两种情况,设环的长度为 C,那么二者的距离为 C-1,:
(a)如果 C-1 为偶数,那么第二圈必定会相遇。
(b)如果 C-1 也为奇数,那么第二圈还是会出现快指针比慢指针多走了一步,等于无限循环了,那么一定不会相遇。
那么总结:若N 为奇数,C 为偶数,则二者一定不会相遇。(什么!!?????)
但是!从下图 2 中的公式的角度看,C 和 N 只能“同偶”或者“同奇‘’,当 N 是奇数的时候,C 只能是奇数,那么 C-1 只能是偶数,所以上述(2)(b)二者不会相遇的情况是不存在的
上图 2 图示公式讲解:fast 走三步,slow 走一步,那么很显然 fast=3slow,而当 fast与 slow 的位置处于上图一的位置时,slow 恰好等于 L,fast等于 L +之前走的圈数乘以圈的长度xC + slow 与 fast 当前位置的距离。那么此式子可变换成如图二,左边是偶数,那么右边也一定是偶数,右边两数相减要得到偶数,只能是“同奇或者同偶”,于是 N 为奇时 C-1 就不可能为奇;
- 于是当 fast 走三步时,也一定会追上 slow,也是可以判断链表是否成环的。
- 虽然证明了无论快指针走几步都会在带环链表中相遇慢指针,但是在编写代码的时候会有额外步骤的引入,所以设计到快慢指针的算法题一般都是默认“快指针走两步,慢指针走一步”。
10——环形链表——2(寻找入环点)
1.大致思路:给出条件“快慢指针相遇后的相遇点,到入环点的距离,与头结点到入环点的距离相等”(稍后证明),那么只需要 首先利用快慢指针判断是否为带环链表,如果是,则此时快慢指针已经相遇,再定义一个指针从头节点开始往后遍历,slow 或者 fast 任意一个指针也同时在环内从相遇点开始往后遍历,两者相遇点即是入环点。
(1)首先判断是否为带环链表,若为带环链表,这次是快慢指针已经相遇
(2)然后创建一个指向头节点的指针pcur,让slow 在环内从相遇点往后遍历, 同时 pcur 同步往后遍历,二者一定会相遇,相遇后即是入环节点,返回 slow 或者pcur 任意一指针即可。
//10——环状链表 2——找入环点
struct ListNode *detectCycle(struct ListNode *head) {
//快慢指针判断是否为带环
ListNode* fast = head,* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
//如果相遇了,则必定可以找到入环点
if(slow == fast)
{
ListNode* pcur = head;
while(pcur != slow)
{
pcur = pcur->next;
slow = slow->next;
}
return pcur;
}
}
//若不为环,则返回空
return NULL;
}
2.证明“相遇点与头结点,到入环点的距离相等”(L = R - X):
如果要证明此例,则从图中可知,要使 L 等于 R-X,而依旧是利用和上一例相同的思路,来证明这个式子,即“快指针等于慢指针的二倍”,经过式子的演变,最后得出L = nR-X(n 为走的圈数),此式也可演变成 L = (n-1)R + (R - X),其实在环中,快指针无论走了多少圈,都不影响相遇点和入环节点的距离;
11——随机链表的复制
大致思路:先在原链表中挨个复制每个节点,并连接在其后,以供后续改变新节点的random 指针,然后挨个改变新节点的random 指针:一般情况下,把新节点 random指向旧节点的 random 的 next 指针,当random 指向空时,直接让其指向 random。最后断开链表即可复制完成。
- 需要创建新建节点的函数,也可单独把复制链表的操作写成一个函数,提高代码简洁性,可读性。
- 也需要处理特殊情况:链表为空是返回 NULL;
(1)在原链表上复制节点:需创建两个指针,pcur 遍历原链表,Next 指针遍历 pcur 的下一个节点(用来插入新节点),同时申请新节点 newnode插入到 Next 处。然后 pcur 往后遍历至原链表下一个节点,Next 走到原链表下一个节点。
(2)置 random 指针:需要三个指针遍历操作,链表复制完成后,让 pcur 指向头节点,创建 copy 指针指向新节点,Next 指针指向下一个原节点。
- 一般情况下,将 copy 中 random 指向 pcur 中 random 的 next,但是当原链表中 random 为空时,不用改变 copy 中 random 即可,因为 random 在复制创建的时候即为空。
- 循环内先把 Next = copy->next;( 在开始先把 Next 置于后一位 ),然后进行 a. 操作(置 random 指针),然后copy = Next->next;(copy 在新链表中后移),pcur = Next;(即可让 pcur 后移),不需要再操作一次 Next 后移,因为会在下一次循环开始时后移。
- 循环直到 pcur 为空(结尾 pcur 会和 Next 处于同一位置)结束。
(3)断开链表:创建三个指针,pcur 指向头节点, newtail 和 newhead 初始均置于第一个新节点。
- 先将pcur 后移,再将 newtail 指向 pcur->next, 然后 newtail 后移一位(虽然新链表复制完成了,但是原链表被破坏了)
- 直至 pcur 为空循环结束,复制完成新节点的 next 本来就是 NULL,所以不用做出改变。
//申请新节点
ListNode* BuyNode(int x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->val = x;
newnode->next = newnode->random = NULL;
return newnode;
}
//复制新链表
void AddNode(ListNode* phead)
{
ListNode* pcur = phead;
//从头开始遍历
while(pcur)
{
ListNode* copy = BuyNode(pcur->val);
//先把 Next 指向原链表的下一位
ListNode* Next = pcur->next;
//然后用三个指针把 copy 插入到原链表
pcur->next = copy;
copy->next = Next;
//pcur 后移
pcur = Next;
}
}
//随机链表的复制操作函数
struct Node* copyRandomList(struct Node* head)
{
//特殊情况:链表为空,直接返回 NULL;
if(head == NULL)
{
return NULL;
}
//复制链表
AddNode(head);
//置 random
ListNode* pcur = head;
while(pcur)
{
//创建三指针遍历链表
ListNode* Next = pcur->next->next;
//Next 保存下一个节点,以供 pcur 后移
ListNode* copy = pcur->next;
//当原链表中 random 不为空时才能对其解引用并置random 到新节点,否则不进行处理,因为其原本即为 NULL
if(pcur->random != NULL)
{
copy->random = pcur->random->next;
}
pcur = Next;
}
//断开连接
pcur = head;
//创建新链表的头尾节点
ListNode* newhead,* newtail;
newhead = newtail = pcur->next;
//当 pcur 走到尾节点时,新旧链表已被断完,新链表的尾节点本来就是 NULL,所以不用多余操作。
while(pcur->next)
{
//破坏原链表,连接新链表
pcur = newtail->next;
newtail->next = pcur->next;
newtail = newtail->next;
}
return newhead;
}