目录
注意:由于翻译原因,在leetcode链表题中,所有的“头结点”指的都是首元结点,而非真正意义上的首元结点
注意:由于翻译原因,在leetcode链表题中,所有的“头结点”指的都是首元结点,而非真正意义上的首元结点
题目1:移除链表元素
解题思路:
思路1:定义一个prev指针,指向需要删除的结点的前一个结点;一个next指针,指向需要删除的结点的后一个结点。最后将判断是否当前结点需要删除的指针叫做pcur。如果pcur当前所指向的并不是需要删除的位置,那么prev以及next同时往后走;反之,则先让prev指向next(为了防止pcur找不到),然后对pcur使用free操作,再让pcur往后走。知道pcur指向空。具体思路如下图所示。
思路2:创建一个新链表newHead,找值不为val的结点,尾插到新链表中。新链表需要有一个指向头的指针,来实现最终的返回;以及一个指向尾的指针,来实现尾插操作。遍历到pcur指向空。请注意,此处所谓的创建新链表只是在逻辑上而言的,实际在内存中只是创建了三个指针变量(pcur、newHead、newTail),而并没有通过malloc动态开辟一个新的链表空间;让newHead指向第一个满足条件的结点,让newTail不断向后查找满足条件的结点。最后也只是指针对于已经开辟的空间进行了一系列的操作,最终返回结果。具体思路如下图所示。
思路1流程图:
思路2流程图:
代码:
typedef struct ListNode listnode;
struct ListNode* removeElements(struct ListNode* head, int val) {
listnode* n1,*n2;//定义两个指针,一个指向原链表,一个指向新链表
listnode* newhead;//新链表的首元结点
newhead=n2=NULL;//新链表初始情况为空,因此首元节点和指向新链表的指针均为空
n1=head;//n1指向原链表的首元节点
if(head==NULL)//原链表为空的情况
{
return head;
}
else //非空的情况
{
while(n1)//循环退出条件:原链表遍历完成
{
if(n1->val!=val)//判断是否不为val值,不是val值的全都放到新链表中
{
if(newhead==NULL)//新链表为空的情况
{
newhead=n2=n1; //创建
}
else //非空的情况
{
n2->next=n1;//尾插
n2=n2->next;
}
}
n1=n1->next;
}
if(n2)//遍历完以后,新链表中有值,为防止情况3,提示语句
{
n2->next=NULL; //为了防止情况1,为val值的结点在原链表最后一位,因此n2的next还是指向了原链表的最后一位
}
return newhead;
}
}
题设是struct ListNode类型,笔者通过typedef改成了listnode。
最后的一个if语句(即代码注释所标注的提示语句),先是为了防止leetcode题设示例1中,6是最后一个结点,直接代替 5 所在结点成为尾结点。如下图所示。
而if语句的进入条件是为了防止示例3,这种情况下,将会对空指针解引用。
题目2:反转链表
解题思路:
思路1:创一个新链表,原链表从头向后遍历,新链表从后往前头插。
思路2:创建三个指针,分别称为n1、n2、n3。初始情况下,n1指向为空,n2指向首元结点,n3指向首元结点的下一个结点。让n2指向n1,然后n1、n2、n3一起往后走1位,重复该操作,直到n2指向为空,具体流程如下图所示。
代码:
typedef struct ListNode listnode;
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL)
{
return NULL; //链表为空
}
else{
listnode* n1,*n2,*n3;
n1=NULL;
n2=head;
n3=head->next;
while(n2)
{
n2->next=n1;
n1=n2;//n1、n2同时往后走
n2=n3;
if(n3) //提示语句
{
n3=n3->next; //n3往后走
}
}
return n1;//循环结束时,n1恰好指向结果链表的首元结点
}
}
代码中往后走的语句出现了“n1=n2”,这会不会导致n2结点指向自己?
答案是不会。n1=n2是让n1的值从NULL变成n2指向的位置,如果要让n2指向自己,需要是n2=n1才行,即n2的下一个结点为n1,n1为n2本身。
使用if语句(即代码注释所标注的提示语句)来限制条件的原因:n2指向尾元结点时,n3已经指向为空;当n2向后移动一位以后,还是经过n3向后移动的操作,这就造成了对空指针解引用。
题目3:链表的中间结点
解题思路:
思路1:遍历原链表,定义一个计数器count。遍历结束以后,直接返回(count/2)结点的next结点。
思路2:使用快慢指针的思路。定义两个指针,一个叫做slow一个叫做fast,初始情况下全都指向链表的首元结点。当链表有奇数个结点时,fast指向尾元结点,slow指向所求结点;当链表有偶数个结点时,fast指向空,slow指向所求结点。
代码:
typedef struct ListNode listnode;
struct ListNode* middleNode(struct ListNode* head) {
//快慢指针
listnode* slow,*fast;
//slow每次走一步,fast每次走两步
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
while(即代码注释所标注的提示语句)语句的两个限制条件,是否可以更改先后次序?
答案是不可以。前一个fast是为了解决偶数个结点的情况,后一个fast->next是为了解决奇数个结点的情况。当把fast->next放在前,又恰好链表是偶数个结点,那就会出现对空指针解引用的情况。所以两者的先后顺序不能改。