一、环形链表
. - 力扣(LeetCode)
判断是否有环,使用快慢指针,开始时都指向头节点,快指针每次走两部,慢指针每次走一步,如果在走的过程中,慢指针和快指针相同(也就是快指针和慢指针指向的节点的同)那么就说明这个链表是带环链表;
原理:
若是这个链表代换,那么快慢指针一定不会走向NULL;只会在这个链表里面循环走下去,并且当循环条件允许时都会进环;同时快指针走的每次都比慢指针多一步,若是一直走下去 ,虽说快指针开始一直在慢指针前面,但是每次快指针都多走一步,最后快指针一定会和慢指针相遇,(就像跑步速度相异的两人在环形跑道上面跑步同理,开始时快的再前面,可能一圈或是两圈之后,跑的快的一定会和跑的慢的相遇)
因此,我们可以把快慢指针相遇作为判断链表是否带环的判断准则,当快慢指针相遇,就说这个链表带环;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
ListNode* fast=head,*slow=head;
while(fast && fast->next && slow)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return true;
}
}
return false;
}
当头节点为NULL,或者是头节点只有一个且这个节点的next为NULL时,不进入循环,直接返回false,可见这种情况也能兼容。
当fast每次走三步或者是更多步呢?(题外话)
先看快指针每次走三步的情况:假设有环,经过上面解法可知,快慢指针相遇一定在环内部;所以我们研究在环中的过程,当慢指针进环的时候快指针已经在换里面走了一会了,我们并不清楚它走了几圈或是多远;假设环可以容纳的节点个数是C(环的长度),假设慢指针刚刚进入环的时候距离环内快指针的距离是N,那么每走一步快慢指针之间的距离就减少2,若是N是偶数,那么快慢指针就能相遇;
若是N是奇数,那么快指针在接近慢指针时相遇的距离是奇数,但是两者每次距离减少偶数2,所以快指针会追上慢指针,并且多出慢指针一个节点的距离;进入第二轮,在环中此时两者之间的距离是(C-1);若是(C-1)是偶数,那么快慢指针最终会相遇;若是(C-1)是奇数,那么会出现和第一轮相同的情况:快指针多出慢指针一个节点的距离,此时两者之间的距离又变成了(C-1);而已知(C-1)是奇数,后面的都是同样如此,这样看来似乎快慢指针永远都不会相遇;
但其实不然:
上面快慢指针不会相遇的情况出现在该条件之下:当N是奇数的时候,C是偶数;这种条件真的存在吗?假设头节点距离进环节点的距离是L,研究慢指针刚刚进环的时刻;此时慢指针走了L,假设快指针在环内走了x圈,那么快指针走的总路程就是L+x*C+N(N是慢指针刚刚进入环的时候与快指针之间的距离),同时又因为快指针走的距离是慢指针的三倍,那么就有以下等式
3*L=L+x*C+N ;
2*L=x*C+N
带入快慢指针永远不会相遇的条件:N是奇数的时候,C是偶数;
因为2*L永远是偶数,而条件带入之后,等式右边无论x是几,等式右边的最终结果都是奇数
由此可知,这种条件不会存在;当快指针每次走的步数更多得到时候,也同样如此;也就是说快慢指针一定会相遇。
二、环形链表 II
. - 力扣(LeetCode)
本题思路:首先判断是否带环,若是不带环就返回NULL;若是带环则返回开始进环时的那个节点指针;判断带环与否参照上一题环形链表;而对于返回进环节点指针,使用下述方法:在判断完带环与否之后我们可以得知快慢指针相遇的节点,记录下这个相遇节点,设置一次循环,让头节点从头开始遍历,相遇节点从相遇的节点开始遍历,若是两个节点相同,那么这两个节点相遇时的节点就是进环时的节点;
原理:
假设进环时的节点和头节点之间的距离是L,fast和slow指针相遇的节点距离进环节点之间的距离是N,,快慢指针相遇之后快指针在环中走了x圈,环的长度是C,把快慢指针相遇的节点称为相遇节点;找等式关系,快指针每次走两步,慢指针每次走一步,快慢指针相遇之后,快指针走的距离是慢指针的两倍,慢指针走了L+N,快指针走了L+x*C+N ;那么就有
2*(L+N)=L+x*C+N;
L+N=x*C;
L=x*C-N;
L=(x-1)*C+C-N;
得出这个关系就说明这种方法可以找到进环节点,解释:L是头节点距离进环节点的距离,头节点从头走起,相遇节点从快慢指针相遇的节点走起,两者每次都走一步,头节点走L时,相遇节点也走了L,也就是(x-1)*C+C-N;不用管(x-1)*C,因为走完(x-1)*C之后回到相遇节点,重点是C-N,我们已知相遇节点和进环节点之间的距离是N,这时已经走过的,未走过的是C-N,那么相遇节点的最后相当于走了C-N,刚好到进环节点;
所以可以利用头节点和相遇节点一起开始遍历、找出两者相同的节点的方法得出进环节点;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* fast=head,*slow=head;
int flag=-1;
while(fast && fast->next && slow)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
flag=1;
break;
}
}
if(flag==-1)
return NULL;
while(head!=slow)
{
head=head->next;
slow=slow->next;
}
return head;
}