例题描述
给定一个带有头结点 head
的单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
- 输入:
[1,2,3,4,5]
- 输出:此列表中的结点
3
(序列化形式:[3,4,5]
)
示例 2:
- 输入:
[1,2,3,4,5,6]
- 输出:此列表中的结点
4
(序列化形式:[4,5,6]
)
结构体定义
struct ListNode {
int val;
struct ListNode *next;
};
思路一
- 快慢指针法:
建立一个快指针pfast
,它的步距为2
。建立一个慢指针pslow
,它的步距为1
,进行循环。
一个周期内,快指针“走2步”,慢指针“走一步”,所以当快指针走到链表尾部时,慢指针此时位于链表的中间结点处。
- 创建快慢指针,使他们指向单链表的
head
指针处。如果是空链表head == NULL
,或者链表中只有一个结点head->next == NULL
,直接返回链表本身的头结点return head
。
- 让快指针先“走一步”,即让
pfast
指向它的next
,然后进行有效性检验,判定此时pfast
是否为空,看它是不是到达了链表的结尾,如果是,则退出循环,返回慢指针。如果不是,即没有到达尾部,则使慢指针pslow
向前走一步。
(因为pfast
总是比pslow
指针要快,所以当pfast
指针有效时,pslow
指针一定有效)
- 慢指针
pslow
指向它的next
之后,让快指针再一次往后“走一步”,同样走完这一步之后需要进行有效性检验。如果此时快指针为空,说明到达链表尾部,跳出循环,返回处于中间结点的慢指针。如果不为空,进行下一轮循环。
第三轮循环中:快指针先走一步,此时没有到达NULL
,然后慢指针走了一步,之后快指针再走一步就指向了NULL
,此时满足循环退出条件,退出了while
循环,此时慢指针位于偶数结点的中间结点(题设返回如果有2个中间结点,则返回第二个中间结点,此时慢指针的指向符合题意)。
- 此时算法满足了偶数结点的情况,但是如果给出的是奇数结点的链表,还能否正确返回中间结点?答案是可以,读者可以自己画草图证实一下。
代码实现
struct ListNode* middleNode(struct ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
struct ListNode *pslow = head;
struct ListNode *pfast = head;
while(1){
pfast = pfast->next;
if(pfast == NULL)
break;
pslow = pslow->next;
pfast = pfast->next;
if(pfast == NULL)
break;
}
return pslow;
}
分析
- 快指针每进一步都要进行有效性检验是非常有必要的,因为每走完一步都有可能走到
NULL
,即链表尾部。如果走完不进行检验,那么作为下一次循环的参数使用,就会发生对空指针解引用的未定义行为,结果不可预期,用户需要尽量避免。所以采用了这种步步为营的方略。
思路二
同样使用快慢指针法。
- 创建快慢指针,指向
head
,与思路一不同的是不进行break
跳出,而是将判断条件提出,单独设置while
条件。 while
循环条件为:此时快指针pfast
不为空,并且快指针的next
也不能为空,即pfast != NULL && pfast->next != NULL
。因为如果不在判断条件中加入当前结点的下一结点指针有效性,一定情境下(奇数结点链表),慢指针就会多走一步,所以这一步判断条件也十分必要。- 其他行径与思路一大致相同,慢指针向前走一步,快指针在走完一步时,此时判定是否到达链表尾部,然则往后走一步。否则不步进,仍然指向
NULL
,等待下一次循环判断跳出。
代码实现
struct ListNode* middleNode(struct ListNode* head) {
if(head == NULL || head->next == NULL){
return head;
}
struct ListNode *pslow = head;
struct ListNode *pfast = head;
while(pfast != NULL && pfast->next != NULL){
pfast = pfast->next;
pslow = pslow->next;
if(pfast != NULL){
pfast = pfast->next;
}
}
return pslow;
}
分析
思路二的代码编写相对于第一种更为直观与人性化,但是也要非常注意while
循环的条件判定,不单单是判断此时的pfast
快指针是否为空,还要判定它的next
指针是否为空,就是为了处理奇数结点链表的情况(如果题设是:若有两个中间结点则返回第一个中间结点,则就要专门处理偶数结点的情况)。