问题是,给你一个单链表判断这个链表是否带环,如果带环返回这个链表的环的首节点,如图
由于链表带环,所以难以直接确定链表的长度,所以无法遍历链表来看是否带环
所以使用快慢两个指针来判断是否带环,如图
快指针每次走两个节点,
慢指针每次走一个节点
如图
当快指针指向第三个节点时,慢指针走到第二个节点
当慢指针走到第三个节点时,快指针刚好转了一圈换回到第三个节点,
此时比较两个指针的地址,如果两个指针的地址相同,则说明带环
如果快指针已经为空,说明遍历链表,则不带环
代码实现如下(只判断是否带环)
struct ListNode
{
int val;
struct ListNode* next;
};
bool hasCycle(struct ListNode* head)
{
if (head == NULL)
{
return false;
}
struct ListNode* slow = head;
struct ListNode* fast = head;
while (fast->next)
{
if (fast->next->next == NULL)//快指针一次前进两个节点
{
return false;
}
fast = fast->next->next;
slow = slow->next;
if (fast == slow)//不能比较val要比较地址,否则有相同的val会出错
{
return true;
}
}
return false;
}
那么问题是
如何证明(环在任意位置,环的大小任意时)
快指针一次走两个节点一定可以和慢指针相遇
同理,快指针一次走3个节点,慢指针一次走一个节点是否一定相遇
同理,快指针一次走n个节点,慢指针一次走一个节点是否一定相遇
给同理,快指针一次走n个节点,慢指针一次走m个节点是一定否相遇
如图
无论环的起点和环的大小快指针必然先进入环
而当慢指针到达环的起点时,快指针必然在环的任意位置,两者间的距离我们记作X
当X==0,直接相遇
当X==1,下次相遇
当X>1, X减1
无论起始位置X和环的大小X每次减1必定刚好减为0
所以fast与slow必然相遇
而当快指针一次走3个节点,满指针一次走一个节点
当X==0,直接相遇
当X==1,则追击距离改为Y-1
追前:
追后
当X==2, 下一次相遇
当X>2,X减2
综上,因为每次减2,
所以当X为奇数时X不会减到0
当X为奇数且Y-1也为奇数时,X永远减不到0
由上可以发现,每次追击X减少(快指针一次走的节点(N)和慢指针一次走的节点(M)的差)
即 一次追击后, X == X - (N - M)
当X == N-M时,下次追上
当N-M>1时则
{ 当 X == N - M -1 追击距离变为Y-1 (num==1)
当 X == N - M -2 追击距离变为Y-2(num==2)
当 X == N - M -3 追击距离变为Y-3(num==3)
.......
直到追击距离变为0或者num+M>N
}
所以
当快指针一次走N次慢指针一次走1次
或者当快指针一次走N次慢指针一次走M次
必然不一定相遇
最后一点,如何返回带环链表的环首节点
有两个方法
第一个是公式法
第二个是把这个问题转换为双链表求交点问题
第一种方法
如图,
我们设环节点的首节点距离链表头节点的节点数为L
设环的大小为C
slow指针与fast指针的相遇点距离环首节点的距离为X(X必然小于环的大小,圈一必定追上,最坏的情况为C-1次追上)
设慢指针走到环的首节点时,快指针走了N圈
则有(快一次走2节点,慢一次走1节点)
当快慢指针相遇时
慢指针走过的距离 == L + X
快指针走过的距离 == L + N * C + X
2 *(L + X)== L + X + N * C
(L + X) == N * C
L == N * C - X
L == (N - 1) * C + C - X //这个等式说明一个指针从相遇点出发,一个指针从链表头节点出发,他们将在环的首节点相遇
如图
代码实现如下
struct ListNode *detectCycle(struct ListNode *head)
{
if (head == NULL)
{
return NULL;
}
struct ListNode* slow = head;
struct ListNode* fast = head;
int flag = 0;
while (fast->next)
{
if (fast->next->next == NULL)//快指针一次前进两个节点
{
return NULL;
}
fast = fast->next->next;
slow = slow->next;
if (fast == slow)//不能比较val要比较地址,否则有相同的val会出错
{
//链表有环
flag = 1;
slow = head;//将慢指针置会链表头节点,快指针指向相遇点
break;
}
}
if (flag)//带环
{
while (1)
{
if (fast == slow)
{
break;
}
fast = fast->next;
slow = slow->next;
}
return fast;
}
return NULL;
}
第二种办法,转变为双链表求交点
将相遇点的下一个节点作为第二个链表的头节点
将相遇点置为空
求原链表与第二个链表的交点即可如图
原文地址:https://www.jb51.cc/wenti/3283503.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。