例题描述
给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
【说明:不允许修改给定的链表】
示例 1:
- 输入:
head = [3,2,0,-4], pos = 1 - 输出:
tail connects to node index 1 - 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
- 输入:
head = [1,2], pos = 0 - 输出:
tail connects to node index 0 - 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
- 输入:
head = [1], pos = -1 - 输出:
no cycle - 解释:链表中没有环。
解题方法
使用快慢指针法,找到相遇点。另外创建2指针,一指针从入口出发,一指针从相遇点出发,两指针相遇点就是入口点。
这其实是一个数学问题:
假设此带环链表抽象成为一个线性结构。题设如下:
-
l为从头结点到相遇点的距离 -
c为从入环点到相遇点的距离 -
n为某个常数,仅代表圈数。(n >= 1)
-
快指针直到相遇共走过的路程为:
l+c+(n*r)。(n代表圈数,大小由步距决定)
慢指针直到相遇共走过的路程为:l+c。 -
此时它们相遇了,说明走过的距离相等,即
l+c+(n*r) = l+c,化简后为l=(n-1)*r+(r-c). -
(n-1)*r项可以忽略,因为在圈内转的圈数没有实际意义,重点还是在于相遇点。所以当一指针从相遇点开始,一指针再次从首结点开始,共同步进,步距相同时,一定会在入环点相遇,因为环外点要走r-c个单位才可以到达入环点,环内点不可能会路过入环点再走一圈,所以环内点走的距离也是r-c,二者刚好在入环点相遇。
代码实现一
ListNode *detectCycle(ListNode *head) {
if(!head){
return NULL;
}
ListNode *p,*q;
p = q = head;
int flag = 0,c = 0;
while(p && p->next){
p = p->next->next;
q = q->next;
c++;
if(p == q){
flag = 1;
break;
}
}
if(!flag){
return NULL;
}
p = q = head;
while(c--){
while(p != q){
p = p->next;
q = q->next;
}
}
return p;
}
代码实现二
ListNode *detectCycle2(ListNode *head) {
if (head == NULL) {
return NULL;
}
struct ListNode *fast = head;
struct ListNode *slow = head;
while (1) {
fast = fast->next;
if (fast == NULL) {
return NULL;
}
fast = fast->next;
if (fast == NULL) {
return NULL;
}
slow = slow->next;
if (fast == slow) {
break;
}
}
struct ListNode *n1 = head;
struct ListNode *n2 = slow;
while (n1 != n2) {
n1 = n1->next;
n2 = n2->next;
}
return n1;
}