链表世界(2),带刷OJ,题型经典而丰富,注重每个细节,动画演示,浅显易懂,爆肝三天,万字博客,巨献之作

链表的秘密(2)第三章

链表专题目录
第一章 链表和链表的分类
第二章 单链表的实现
第三章 链表OJ题
第四章 双向带头循环链表的实现
第五章 总结和对比链表和顺序表
下面是用OJ题对我们学习的内容进行巩固和更深入地去理解链表,总共12个基础题目,链表还有更多的问题需要我们以后学了更深入的知识才能解决。即使是12个基础题,也不是投怀送抱的送温暖的题目,其中第7到11题是拔高题有一定难度但要学好链表,也需要掌握


1.删除链表中等于给定值 val 的所有结点。

OJ链接
在这里插入图片描述
题目的意思很简单,结合测试用例我们可以理解,返回删除后的链表头结点即可。如果没有元素或是全部删完了就返回空。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LTNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    //创建新链表的头和尾
    LTNode* newhead,*newtail;
    newhead=NULL;
    newtail=NULL;
    LTNode* pcur=head;
    while(pcur)
    {
        if(pcur->val!=val)
        {
            if(newhead==NULL)
            newhead=newtail=pcur;
            else
            {newtail->next=pcur;
            newtail=newtail->next;}
        }
        pcur=pcur->next;
    }
    if(newtail)
    newtail->next=NULL;
    return newhead;
}

结合我们上一次学到的链表的节点删除操作,这题就是一个实际的应用。遇到要删的节点的时候执行删除操作。不过是引入newhead和newtail节点,这是为了对链表进行操作。最后newtail后面跟上一个下一个节点置空的尾巴即可,下面有一个动画帮助大家理解上面的代码:
在这里插入图片描述
画的有点拙劣和简单还望大家多多包容(* ̄︶ ̄)
其实代码的意思就是if中套个if刚开始newhead为空的时候,把newhead,newtail定到头结点上,再让pcur一步步往下遍历,如果遇到val节点就跳到下一个节点,同时
newtail改变原链表节点的指向
,否则就让newtail跟着pcur就好。到最后,返回newhead头结点就好。

2.翻转链表

OJ链接
OJ
意思浅显易懂,题目又让人有一种熟悉之感。我们在学习数组的时候是不是也做过类似题目呢?死去的记忆又燃了起来,无论是逆置还是字符串逆序。我们当时采用了一种办法叫做左右指针法,内容交换往中靠拢,结束条件是left>right。那现在在链表的场景中我们要如何解决这样的问题呢?
所以我们引出一种新的解题方法,三指针翻转法来搞这个题目:
先看过程:
在这里插入图片描述

然后,我再补充一些细节的讲解,用三指针的好处是有益于我们记住前后的节点,否则我们翻转链表改变节点指向后,就会找不到原来节点的next节点,循环结束的条件是判断n3是否为空,其次是n2,是否为空,都为空的时候,新的链表也就形成了。(中间补充了一个链表本身有的尾空指针NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode *slow, *fast;
    slow = fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
typedef struct ListNode LTNode;
struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL)
        return head;
    // 可以不额外创建pcur
    LTNode *n1, *n2, *n3;
    n1 = NULL;
    n2 = head;
    n3 = head->next;
    while (n2) {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if (n3) {
            n3 = n3->next;
        }
    }

    return n1;
}

还有就是如果链表为空就不用翻转了,在最前面加一个判断直接返回NULL就可以了。

3.链表的中间节点

OJ链接
在这里插入图片描述
找中间节点,问题的关键其实就是怎么找?人看链表一目了然,但是计算机怎会知道何时应该停下呢?哪个才是要找的中间节点呢?
所以我们不仅需要一个指针指向我们要找的中间节点到时候返回,还要一个指针去判断我们何时找到,何时结束返回。于是这是,我们仍然要请出我们的双指针法出山了。
这个题目给大家演示奇数个节点和偶数个节点两种情况
1.奇数个链表节点数:
在这里插入图片描述
发现了吗,结束条件是fast->next等于NULL
2.偶数个链表节点数:
在这里插入图片描述
发现了吗,这个时候是fast==NULL的时候结束
所以说,找中间节点的终止条件应该是(fast!==NULL&&fast->next!=NULL)
但是,如果倒过来,会出现问题。对比第二种情况,是先判断fast不为空的前提下,结束判断跳出循环,这是C语言阶段我们学过的短路性质。如果前置,(fast->next!=NULL&&fast!==NULL)那fast->next就是一个对空指针的解引用访问,出现了Bug,这也提醒我们,C语言阶段的基础打牢了,才能在以后的阶段能够精准判断少犯错误。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LTNode;
struct ListNode* middleNode(struct ListNode* head) {
    LTNode* slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

4. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。

OJ链接
在这里插入图片描述
这个题目我们的思路会在开始就卡住,两个链表,是值小的那个做头结点,开始往下去链,最后返回头结点。但是,到底是第一个链表还是第二个链表是不确定的,写代码的时候可以硬写,写分支语句来决定returnl1,还是l2.但是这样会显得我们代码很挫。不妨,用我之前讲的一个点,就是哨兵位,能很好地优化我们的代码。哨兵位只存储指向下一个节点的指针,而不存储有效的值。我们让哨兵位做头结点直接返回它下一个节点即可,思路还是不变的,谁小链谁就是了
同样,我们来演绎这道题的思路和过程:
在这里插入图片描述
这题的newtail是跟着整个合并链表的过程走的,开始之前又忘记给两个链表加上NULL了,后面补上,还请见谅,谁小就链接谁,同时newtail跟上,改变下一个节点的指向。链完后,让L1,L2指针往下走一步,直到遇到空指针为止,L1,L2的存在是为了判断什么时候哪一串链表先链完,不断重复谁小链谁的过程。像上面,第二串链表的节点已经全部链完了,那就把第一串剩下的那些节点接上就可以了。最后返回newhead->next,完毕。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode LTNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    LTNode *l1, *l2;
    l1 = list1;
    l2 = list2;
    if (l1 == NULL) {
        return l2;
    }
    if (l2 == NULL) {
        return l1;
    }
    LTNode *newhead, *newtail;
    newhead = newtail = (LTNode*)malloc(sizeof(LTNode));
    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;
        }
        if (l1 == NULL) {
            newtail->next = l2;
        }
        if (l2 == NULL) {
            newtail->next = l1;
        }
    }
    return newhead->next;
}

一样的,只要这两个链表有一个为空,那就直接返回另一个链表的头结点,省事。都为空的话,就返回空。

5.链表的回文结构。

OJ链接
在这里插入图片描述
这个题目有点像前面讲过的那个翻转链表,它也是从字符串回文的题目变来的,只不过换成了链表。但是,却没有像字符串回文判断那么方便了,因为单链表的不可逆性。所以我们就只能另想办法解决了,题目供给了一个单链表,那我们只能对这个单链表来操作实现判断。
回想我们做过的前面有两个题目:链表的中间节点,链表的逆置。或许这两个小零件的组合能造出我们的变形金刚,这道题给我们带来的”新大陆”的解法。
这题牛客只给我们提供了C++的环境,不过莫慌C++是兼容C的,我们仍可以用C的解法,只要注意在C++中空指针的表示用nullptr就成。
那怎么利用前面写过的函数来搞定这个新题呢?
在这里插入图片描述
像上面这个情况,搞L1,L2两指针,让他们指向的元素分别去比相不相等,直到结束,如果都相等,就是回文链表了。一旦中间哪里不相等,那就不是回文链表
不过,这边要用到我们之前写的两个函数,我们不像重新写,那就做一会CV工程师,CV一下之前写的代码配置一下新的函数接口就好了。


struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}
struct ListNode* reverseList(struct ListNode* head) {
    if(head==nullptr)
    return head;
    //可以不额外创建pcur
    struct ListNode* n1,*n2,*n3;
    n1=nullptr;n2=head;n3=head->next;
    while(n2)
    {
        n2->next=n1;
        n1=n2;n2=n3;
         if(n3)
    {
        n3=n3->next;
    }
    }
    return n1;
}
class PalindromeList {
    //结合找中间节点和反转链表两个小题
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        struct ListNode* mid=middleNode(A);
        struct ListNode* rmid=reverseList(mid);
        while(A&&rmid)
        {
            if(rmid->val!=A->val)
            return false;
            else
             {
                A=A->next;
                rmid=rmid->next;
            }
            
        }
        return true;
    }
};

6.链表的第一个公共节点

OJ链接
在这里插入图片描述
大家需要点进OJ结合示例去理解这道题的意思。
一看到这题,有的朋友会这样想:这还不简单吗,你看这图,遇到相交节点不就是值相等吗,那也太好办了。
等等!!!
在这里插入图片描述
1和1相等,但是,相交点的值是8呢!
所以说,这道题万万不可草率地直接用值相等去判断,那会把人给坑死。测试用例里面一定会有那种很坏的例子,比如,所有节点的值都是1。但是,有相交的,也有不相交的。所以说,要判断是否相交,我们要比较的应该是节点的指针,而非节点的值。当指针都指向同一个节点,也就是指针和指针存储的地址相同时,链表才是相交的。
我们还知道一点,单链表节点,只有一个next指针,指向下一个节点。那就意味着,他们如果在同一个节点相交,以后的节点,要么为空,要么以后的节点的指向都完全一样,每个节点一一完全相同。就像两条河流汇成一条河流后不再分开了。
在这里插入图片描述
所以我们能确定的一点就是**,如果链表相交,那么直至分别遍历结束前的NULL节点前面的那一个节点一定完全一样。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/abb589ff8e3f49bc8aee7c7e48b3c2c2.png
我们只要让他们分别遍历,看NULL前那个节点指针是否相等就可以了。
下一个问题,如果链表相交,怎么去找相交节点?
注意到,链表A和链表B可能一样长,或者有差,那么,我
们可以让长的链表对应的指针(headB)先出发,短链表的指针(headA)后出发,直至它们撞个满怀,就是相交节点了**。
同样配个动画,让大家好好理解:
在这里插入图片描述
找相交节点:长链表指针先走几次,取决于gap的值,每走一次,gap–,直到为0,两者一起走
在这里插入图片描述
有一个小细节,在代码里有这么一段:
在这里插入图片描述
这是很有用的技巧,在以后我们玩栈和队列,二叉树的堆的时候还会用到。不知道谁大谁小,谁长谁短,就不妨假设A小B大,如果事实是A大B小,是相反的,那就A,B互换,A做大链表,B做小链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LTNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    LTNode* curA,*curB;
    curA=headA;
    curB=headB;
    int lenA=0;
    int lenB=0;
    while(curA->next)
    {
        lenA++;
        curA=curA->next;
    }
    while(curB->next)
    {
        lenB++;
        curB=curB->next;
    }
    if(curA!=curB)
    {
        return NULL;
    }
//判断完有相交节点之后,找相交节点
    LTNode* ShortList=headA;
    LTNode* LongList=headB;
    if(lenA>lenB)
    {
        ShortList=headB;
        LongList=headA;
    }
    int gap=fabs(lenA-lenB);
    while(gap--)
    {
        LongList=LongList->next;
    }
    while(ShortList!=LongList)
    {
        ShortList=ShortList->next;
        LongList=LongList->next;
    }
    return LongList;
}

7.带环的链表,判断是否有环

OJ链接
在这里插入图片描述
直观理解,链表带环的话,那你的指针就会在里面一直跑出不来。如果没有环,无论这个环有多大,总有一天,你的指针会从环里面跑出来,最终遇到NULL
如果搞两个指针的话,有环的话,那两个指针就会在环里边转悠,那到底会发生什么呢?
让我们来仔细分析一下:
首先,我们明确了用双指针去分析这个题目,如果两个指针速度都一样,你走一步,我走一步,那我们都在环里面,只要我们隔着一定的距离,那就永远遇不到你,除非你在环的路口等我,我们有缘分,我一进环就和你相遇。所以说速度一样,没法追上。
那么,速度不一样的时候,我就有追上你的可能,至于是否一定能追的上,看的是数学,更是造化。一个藏在编程里的数学问题浮出水面,且听我慢慢到来:
第一件事情,我们假设每过一秒,快指针走两步,慢指针走一步。那么,每秒钟,快指针和慢指针之间的距离缩小1。两指针都在环里面绕,最终,快指针将追上慢指针。所以,我们就有以下的代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LTNode;
bool hasCycle(struct ListNode *head) {
    LTNode* slow,*fast;
    slow=head;fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        return true;
    }
    return false;
}

同样要注意,fast&&fast->next顺序不能交换,这点的原因有在前文讲链表中间节点那道题目的时候提到。
以此看来,这道题目本身似乎不然,代码也很短。但事实上,代码的长短并不是衡量一道题难易的东西。到后面学到二叉树,排序就会发现,短短几行代码需要我们极大思维量去破解和分析。这道题的扩展出来的问题,不止是7,8两个问,如果快指针一次走三步,四步,五步,那最终的结果是什么呢?是会相遇还是谁追上谁?
好,第二件事情,我们就以快指针一次走三步为例,重新来分析一下这个问题
Q:到底两个指针会不会相遇。如果相遇,相遇的情况是什么?是快指针追上慢指针,还是慢指针追上快指针?
A:给大家做了一个详细的解析:
在这里插入图片描述
在这里插入图片描述
插入一个细节的动画:
在这里插入图片描述
新一轮的追击距离是C-1.

在这里插入图片描述
当然快指针一次四步,五步的分析方法也是类似的。大家可以在掌握上述分析方法的基础上尝试去分析一下。对应是什么情况。

8.在7的基础上,尝试返回环的第一个节点或NULL

OJ链接
在这里插入图片描述
基于第7题的第一件事情分析(快指针一次两步,慢指针一次一步的情况)和代码分析,我们才能更好地分析这个题目
在这里插入图片描述
将分析写成代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* hasCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    slow=head;fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        return slow;
    }
    return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
    if(hasCycle(head)==NULL)
    {
        return NULL;
    }
    struct ListNode* meet=hasCycle(head);
    while(meet!=head)
    {
        head=head->next;
        meet=meet->next;
    }
    return meet;

}

9.分割链表

OJ链接
在这里插入图片描述
这题的总体思路就是设置两个链表,一个是大链表,一个是小链表,意思就是小链表放的是小于x的节点,大链表放的是大于等于x的节点,同时两组newhead和newtai指针去改变节点指针的指向,因为题目说要不改变原顺序,所以采用遍历的方法即可。两组newhead相当于哨兵位,最后把小链表尾插到大链表即可。
注意,最后大链表尾插上去是要补上NULL节点的,不然,新组合的链表没有NULL就找不到尾了。
在这里插入图片描述

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/

#include <cstdlib>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        struct ListNode*lesshead,*lesstail,*greaterhead,*greatertail;
        lesshead=lesstail=(struct ListNode*)malloc(sizeof(struct ListNode));
        greaterhead=greatertail=(struct ListNode*)malloc(sizeof(struct ListNode));
        // write code here
        struct ListNode*cur=pHead;
        while (cur) {
        if(cur->val<x)
        {
            lesstail->next=cur;
            lesstail=lesstail->next;
        }
        else {
            greatertail->next=cur;
            greatertail=greatertail->next;
        }
        cur=cur->next;
        }

        lesstail->next=greaterhead->next;
        greatertail->next=nullptr;

        pHead=lesshead->next;
        free(lesshead);
        free(greaterhead);
        return pHead;
    }
};

10.链表的深拷贝

OJ链接
在这里插入图片描述
这道题让人头疼的地方正是复制链表的指针不能指向原点,也就是题目里面加粗的那句话,也就是要完完全全拷贝节点重新拼接,所以说,这个题目又是一个解法,算法特殊的题目。倘若没接触过相似题目,是很难解出这到题目的
我们先来看怎么解这道题:
在这里插入图片描述
第一步是复制链表,演示前三个的复制过程
在这里插入图片描述
第二步是调整random指针,结合代码和画图体会调整过程
在这里插入图片描述
录完发现cur和copy忘跟上了,不过视频后面有补充说明怎么动的,这道题的视频是12题里面最难录的一个,算法比较复杂,也不是很好理解,但还是需要大家掌握,这个题目比较重要。结合代码语句才能更好地分析出算法的思想和过程。

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node LTNode;
struct Node* copyRandomList(struct Node* head) {
	//1.复制链表
    LTNode* cur=head;
    while(cur)
    {
        LTNode* copy=(LTNode*)malloc(sizeof(LTNode));
        copy->val=cur->val;


        copy->next=cur->next;
        cur->next=copy;
        cur=cur->next->next;
    }
    //2.调整随机指针
    cur=head;
    while(cur)
    {
        LTNode *copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        cur=cur->next->next;
    }
    cur=head;
    //3.解下原链表,创建新链表
    LTNode* newhead,*newtail;
    newhead=newtail=NULL;
    while(cur)
    {
        LTNode* copy=cur->next;
        LTNode* next=copy->next;
        if(newtail==NULL)
        {
            newhead=newtail=cur->next;
        }
        else{
            newtail->next=cur->next;
            newtail=newtail->next;
        }
        cur->next=next;
        cur=next;
    }
    return newhead;
}

11.约瑟夫环问题

OJ链接
在这里插入图片描述
这个题目很有意思,2024年春晚魔术原理其实就是这个题目变化来的,出自的也是一个数学问题。所以说,数学还是和我们工科很是息息相关的,算法对数学的要求,像微积分,线性代数中矩阵转置,变换还是很高的。
当时小尼的扑克牌没对上,其实就像极了程序的bug和报错
大家可以在魔术中感受数学的魅力,同时在融进计算机程序分析,感受学科交融的魅力和神奇,
不管在数学,还是计算机领域,他都可以作为一个大的问题的存在去深入扩展和分析。有兴趣的朋友可以去百度一下后面的数学原理和代码逻辑。
回到这题,这个题目起源于一个古代故事,不知道是不是真实存在:
著名的Josephus问题
据说著名犹太历史学家 Josephus有过以下的故事:在罗⻢⼈占领乔塔帕特后,39 个犹太⼈与
Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被⼈抓到,于是决定了⼀个⾃杀⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀,然后再由下⼀个重新报数,直到所有⼈都⾃杀⾝亡为⽌。
然⽽Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在第16个与第31个位置,于是逃过了这场死亡游戏。

于是乎,我们就建模出上面那个OJ问题
举个简单例子帮助大家理解:
从1开始报数,最后活下来的是4号
在这里插入图片描述
那怎么用代码实现呢?
这里,我们用了一个哨兵位prev,防止m=1的情况最后删空,如果m=1,那最后返回prev就可以了,count相当于报数的计数器,每到m就执行退出操作并清零重新执行一轮。prev的next作为新的头结点,报1数的人继续,直到最后只剩一个人,就是pcur的next指针指向自己的时候返回,游戏结束

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */
 typedef struct ListNode LTNode;
 LTNode* BuyNode(int x){
    LTNode* newNode=(LTNode*)malloc(sizeof(LTNode));
    newNode->val=x;
    newNode->next=NULL;
    return newNode;
 }
LTNode* createList(int n){
    LTNode* phead=BuyNode(1);
    LTNode* ptail=phead;
    for(int i=2;i<=n;i++)
    {
        ptail->next=BuyNode(i);
        ptail=ptail->next;
    }
    ptail->next=phead;
    return ptail;
}
int ysf(int n, int m ) {
    LTNode* prev=createList(n);
    LTNode* pcur=prev->next;
    int count=1;
    while(pcur->next!=pcur)
    {
        if(count==m){
            prev->next=pcur->next;
            free(pcur);
            pcur=prev->next;
            count=1;
        }
        else
        {
            prev=pcur;pcur=pcur->next;
            count++;
        }
    }
    return pcur->val;
}

12.删除链表的倒数第K个节点

OJ链接
在这里插入图片描述
这题放在这个位置相当于一个放松的题目了,这题也可以用快慢指针法快指针先走k步补步差,慢指针再走,也可以纯硬雷用sz-n,一个指针走下去找到倒数第k个,也就是正数sz-n个之后尾删或是头删操作。我自己第一次写的时候就是硬雷,没有用快慢指针的技巧方法,会稍显拙劣些,但也能通过。这题在之前是一个数组版本,就是数组的倒数第K个元素,方法类似。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* pcur=head;
    int sz=1;
    while(pcur->next)
    {
        sz++;
        pcur=pcur->next;
    }
    if(sz==1)
    return NULL;
    int ret=sz-n;
    struct ListNode* pcur2=head;
    if(ret==0)
    {
        struct ListNode* pcur5=head;
        head=head->next;
        free(pcur5);
        pcur5==NULL;
        return head;
    }
    while(ret)
    {
        pcur2=pcur2->next;
        ret--;
    }
    struct ListNode* pcur3=head;
    while(pcur3->next!=pcur2)
    {
        pcur3=pcur3->next;
    }
    struct ListNode* pcur4=pcur2;
    pcur3->next=pcur2->next;
    free(pcur4);
    pcur4=NULL;
    return head;
}

快慢指针方法;

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* fast,*slow,*cur;
    fast=slow=head;
    cur=head;
    int sz=1;
    while(cur->next)
    {
        sz++;
        cur=cur->next;
    }
    //只有一个节点
    if(sz==1)
    {free(cur);
    cur=NULL;
    return NULL;}
    while(n--)
    {
        fast=fast->next;
    }
    //正数第1个节点头删
    if(fast==NULL)
        {
            head=slow->next;
            free(slow);
            slow=NULL;
            return head;
        }
        //非正数第一个节点
    while(fast)
    {
        fast=fast->next;
        slow=slow->next;
    }
    struct ListNode* pcur=head;
    while(pcur->next!=slow)
    {
        pcur=pcur->next;
    }
    pcur->next=slow->next;
    return head;


}

总结

想必大家经过上面链表OJ的洗礼之后,对链表也没有那么畏惧而望而却步了。是不是觉得有点好玩,难度也还可以,如此,挺好。若是显得有点吃力,那还得加把劲反复练习一下,争取彻底地消化吸收和理解,到后面,你在很多大魔王的数据结构面前就会发现,链表这种的东西是多么的可爱了。那么,我们这期的内容就到这里,本期内容作者肝了三天时间,呈现巨献不易,还请大家多多关注,一键三连,评论支持呀,我们下期再见

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值