
反转链表,顾名思义即为将链表颠倒,有一个非常容易想到的思路,就是你创建一个新链表,然后逐个遍历原链表的节点,逐个头插进新链表中。但我今天不讲这个方法,我要为大家讲解一种在原链表上原地反转的方法。
其实说插入链表总共要考虑什么呢,新节点的指向,前一个结点的指向;那么说删除节点又要考虑什么呢,其实就是前一个节点的指向;说来容易,那我们这里的反转,无非要考虑的情况也就那么几种,首先我们要找到当前节点,这是肯定的吧,光有当前节点肯定不够,我们还需要当前节点的前一个结点和后一个节点,前一个结点你能理解,因为你要修改当前节点的指向,改成原先的前一个结点,那后一个节点是为啥呢?其实很好想,当你将当前节点的指向修改后你已经无法找到原来的下一个节点了,所以必须提前拿到后手动赋值以免循环无法进行。那么赋值又该怎么赋?无非是整体后移一位,即prev指向上一次的now,now指向next,next指向新的next;那么循环啥时候截止?当now为空时,代表你已经完成任务,链表里已经没有元素了,此时截止即可。但是还有细节不要忘记,就是我们是要改变头节点的朝向的,一定记得最后将prev的值赋给头节点,为啥是prev?还记得我们说now为空时循环终止条件吗,prev自然是第一个元素。照例考虑临界情况,首先链表只有一个元素怎么办?直接返回这个节点即可,那如果链表为空呢?那直接返回NULL就可以了吧。代码实现如下
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
{
return NULL;
}
LN* pnow=head->next;
LN* prev=head;
head->next=NULL;
while(pnow)
{
LN* next=pnow->next;
pnow->next=prev;
prev=pnow;
pnow=next;
}
return prev;
}
接下来第二题

移除链表元素,跟上一篇我讲的移除顺序表元素很像,但其实更加简单了,因为这个无非就是在考你链表指定位置的删除,因为不同于数组,链表删除是十分便利的,根本就涉及不到元素的挪动问题,所以其实这道题没啥好说的,我直接放代码
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
struct ListNode* removeElements(struct ListNode* head, int val) {
LN* pnow=head;
LN* prev=NULL;
while(pnow)
{
LN* next=pnow->next;
if(pnow->val==val)
{
if(pnow==head)
{
head=next;
}
if(prev)
{
prev->next=next;
}
}
else
{
prev=pnow;
}
pnow=next;
}
return head;
}
第三题

中间节点,这里我要给大家介绍一个非常重要的思想,快慢指针,快指针走两步慢指针走一步,最后快指针无法再走时慢指针一定指向中间节点,这就跟物理速度差不多,你速度二倍时间一样,路程可不就是二倍关系吗。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
LN* fast=head;
LN* slow=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
第四题

这道题我选择创建新链表逐个尾插的方式,同样是定义两个指针遍历两个链表,然后定义新的头节点,但是这里注意,创建新链表只是存在于逻辑上,并不是真的开辟了一块空间存储数个节点,而是在原链表上进行操作,只是形如建立新链表的样子,待到一方为空则直接把非空链表的剩余节点尾插即可,代码如下
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
LN* l1=list1;
LN* l2=list2;
if((l1==NULL && l2!=NULL)||(l1!=NULL && l2==NULL))
{
return l1==NULL?l2:l1;
}
LN* newhead=NULL;
LN* newtail=NULL;
while(l1&&l2)
{
if(l1->val < l2->val)
{
if(newhead==newtail)
{
if(newhead==NULL)
{
newhead=l1;
newtail=newhead;
}
else
{
newtail=l1;
newhead->next=newtail;
}
}
else
{
newtail->next=l1;
newtail=newtail->next;
}
l1=l1->next;
}
else
{
if(newhead==newtail)
{
if(newhead==NULL)
{
newhead=l2;
newtail=newhead;
}
else
{
newtail=l2;
newhead->next=newtail;
}
}
else
{
newtail->next=l2;
newtail=newtail->next;
}
l2=l2->next;
}
}
if(l1==NULL)
{
while(l2)
{
newtail->next=l2;
newtail=l2;
l2=l2->next;
}
}
else
{
while(l1)
{
newtail->next=l1;
newtail=l1;
l1=l1->next;
}
}
return newhead;
}
第五题

这应该是最复杂的一道题,但是并不算难,只是步骤比较多,我们捋一下这道题,首先他说围成一圈,其次我们需要不停的去淘汰节点,或者说删除节点,那自然不能使用数组,也不能使用普通的单链表,因为达不到首尾相接的效果,这时候就要想到循环链表,即尾节点指向头节点以达到循环效果,那么解决了选用数据结构的问题,接下来开始正式梳理这道题,看到这道题的淘汰制,一定第一时间想到删除,单链表节点的删除需要三个节点,循环链表其实也是一样,在这里区别不大,因为我们是单向循环,这是底层逻辑,然后针对这道题,我们要考虑报数的机制,从1开始,逐个遍历,遍历一个节点加1,达到目标后停止,并删除相应节点,然后就要接着遍历,从1接着开始,循环往复直到链表中只剩一个节点,这里的删除我要简单说一句,删除节点后,prev是不能动的,你想,你删掉了这个节点,那么下一个节点的上一个节点不久改成prev了吗,其他的部分其实没什么特别需要注意的地方,具体可以参考下面代码
cpp
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @param m int整型
* @return int整型
*/
typedef struct ListNode LN;
int ysf(int n, int m ) {
// write code here
//成环
LN* newhead=(LN*)malloc(sizeof(LN));
LN* pcur=newhead;
LN* ptail=newhead;
newhead->val=1;
newhead->next=ptail;
for(int i=2;i<=n;i++)
{
LN* newnode=(LN*)malloc(sizeof(LN));
newnode->val=i;
pcur->next=newnode;
pcur=pcur->next;
}
pcur->next=newhead;
ptail=pcur;
LN* prev=ptail;
LN* pnow=newhead;
LN* next=newhead->next;
int cnt=0;
while(pnow!=prev || pnow!=next)
{
cnt++;
if(cnt==m)
{
prev->next=next;
free(pnow);
pnow=next;
next=pnow->next;
cnt=0;
}
else
{
prev=pnow;
pnow=next;
next=pnow->next;
}
}
return pnow->val;
}