一:判断链表是否带环
思路:
用快慢指针从头结点开始向前走,slow每向前走一步,fast向前走两步。如果最终fast和slow相遇,就说明链表带环;如果最终fast或fast->next为NULL指针,链表就不带环。
原理:
链表不带环:fast会先到达尾结点,结束。
链表带环:fast比slow较早进环,当slow到达环结点,fast有两种情况:
fast刚好绕环n(n>=1)圈后,走到环结点,slow和fast相遇
当slow走到环结点时,fast在环内,假设fast和slow的距离为n,fast和slow继续向前走,每走一次,fast和slow的距离-1,n次后fast和slow相遇
![](https://i-blog.csdnimg.cn/blog_migrate/957763c93832a426241d906ecb2ce20b.png)
思考:
如果slow每向前走一步,fast向前走m步,快慢指针会不会相遇?
答案是不一定,以m=3为例:
设fast与slow相距n,环的周长是c,当slow到达环节点后,由于fast与slow的相对速度是2
对n分类讨论:
n是偶数,继续向前走,刚好可以相遇
n是奇数,继续向前走,当fast与slow相距1时,二者再向前走一次,fast会和slow”擦肩而过“。这时,fast与slow相距c-1,又要对c-1分情况讨论:
c-1是偶数,继续向前走,可以相遇
c-1是奇数,fast和slow又会“擦肩而过”,而且之后快慢指针错过后,二者的距离都是c-1,它们永远不会相遇
代码:
struct ListNode {
int val;
struct ListNode* next;
};
bool hasCycle(struct ListNode* head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
return true;
}
}
return false;
}
二:找相遇结点的指针和头指针的相遇结点
思路:
分别用两个指针存储头节点和快慢指针的相遇节点,让它们同时向前走,最终相遇的结点就是链表环结点
原理:
设头结点与环结点距离为L,slow和fast相遇时,slow与环结点距离为x,环的长度是c,则当slow与fast相遇时:
slow走的距离是:L+x
问题:slow可能在环内走一圈吗?
不可能,因为在slow进环的时候,fast与slow的距离最大是c-1,slow最多走c-1次就可以与fast相遇,此时x=c-1
fast走的距离是:L+nc+x
n表示在fast与slow相遇前,fast在环内走的圈数
对n的理解:当L远大于c时,在slow没入环前,fast会在环内走很多圈,n将是一个很大的数;当c远大于L时,slow入环前,fast在环内一圈也没走,之后fast追slow会走一圈,所以n = 1。
根据快慢指针的关系,fast走的距离是slow的2倍,得出下面关系式:
L = (n-1)c + (c-x)
c-x是相遇结点到环结点的距离,由关系式得出:slow与fast的相遇结点,与头节点同时向前走,一定会相遇
理解:当头结点走L距离也就是到达环结点时,相遇结点的指针刚好绕环走了n-1圈回到相遇点,并继续走了c-x,也到达了环结点,与头节点相遇
代码:
struct ListNode
{
int val;
struct ListNode* next;
};
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
struct ListNode* meet = fast;
struct ListNode* first = head;
while (meet != first)
{
meet = meet->next;
first = first->next;
}
return meet;
}
}
return NULL;
}