Given a linked list, return the node where the cycle begins. If there is no cycle, return null
.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?
一、算法分析
设置两个指针,slow fast分别步进为1和2;如果fast变为null,那么说明无环;如果fast和slow相等,说明有环;
该类单链表的环问题一般有如下几种:
(1)判断单链表是否有环
(2)求单链表环的长度
(3)求单链表环的起始结点
(4)求单链表的总长度
解法:
(1)判断单链表是否有环,可以设置快慢指针,如果相遇了,则说明有环。下面证明为何两者一定会相遇:
假设单链表环长度为n,慢指针p 和 快指针q 同时进入环。那么经过i步之后,p位置为 i mod n ,q位置为 2i mod n 。假设两者能相遇,则有 i mod n = 2i mod n; 即 (2i - i )mod n ==0 ==> i mod n ==0, ==> i = n。也就是说两者在起点相遇,此时慢指针正好走了一圈。这个也很好理解,两个人去操场跑步,在同一个起点开始跑,跑的快的是慢的速度的两倍。 那么当第一个人跑一圈的时候,第二个人正好跑了两圈,他们正好在起点相遇。
那么当两者不是同时进入环的话,情况会怎么样那?假设两者相差 k ,i 步之后,p位置为 i mod n ,q位置为 (2i + k)mod n 。若要两者位置相同,则需要 i mod n = (2i + k) mod n ==> (2i + k - i) mod n==0 ==> (i + k) mod n ==0 ==> i+k==n ==> i=n-k; 也就是说相遇位置在 n-k处。
(2)求环的长度
利用上面的办法找到了环里面的点,记录下该点,然后继续遍历,当相等时正好遍历环一圈,正好为环的长度
(3)求单链表环的起始结点
设头结点离环起始结点距离为k ,那么当 慢指针p 刚好走到起始结点时,快指针已经位于 环中的 k 处(因为快的是慢的速度的两倍,所以走的路程是2k)。根据(1)中的讨论,此时两者相遇的地方为 n -k 。 因此从头结点 和 碰撞点 同时走,走过k步之后,相遇的地方就是起始点。
(4)求单链表的总长度
求起始结点时,只需记录下从头结点走的步数,然后加上环的长度。
二、C语言实现
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *detectCycle(struct ListNode *head) { struct ListNode *slow,*fast; bool isCycle = false; if(head==NULL || head->next==NULL) return NULL; slow=head; fast=head; while(slow!=NULL && fast!=NULL){ slow=slow->next; if(fast->next==NULL) return NULL; fast=fast->next->next; if(slow==fast){ isCycle = true; break; } } if(isCycle){ slow=head; while(slow!=fast){ slow=slow->next; fast=fast->next; } return slow; }else{ return NULL; } }