所谓带环就是一组链表种最后一个结点的next指针不指向NULL而是指向该链表的某个结点。
eg:
如何判断一个链表是否带环?
以目前阶段所学知识,快慢指针是唯一方案。
意思就算是先定义一个快指针,和一个慢指针,让快指针每次走两步,慢指针每次走一步。
如果当慢指针将要进入环中时,快指针已经在园内某个位置了。
在当快指针与慢指针相遇时,即可判断该链表带环。
如果不带环,则两则相遇是值next值都为NULL,也就是链表最后一个节点。
判断带环链表
看到这里简单做个题吧:141. 环形链表 - 力扣(LeetCode)
经过读题很简单就是判断是否为环形链表,结合我们上述分析如何判断带环,很容易写出。
对应代码:
struct ListNode* slow=head;
struct ListNode* fast=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){
return true;
}
}
return false;
引发出的问题(在有环的情况下)
1.为什么快慢指针一定会相遇?有没有会错过,永远也相遇不了?
2.快指针走两步可以,一次走三步,四步,五步,是否也可以?
证明:
快指针一次走两步,慢指针一次走一步
假设慢指针刚进环时,快慢指针距离为N,当慢指针走一步快指针走两步时,他们的距离会减小1,当他们距离减小到0时,他们就相遇了。因此可得快指针最终会追上慢指针的。
证明2:
当fast指针每次走三步时
当快慢指针距离为N时 他们的距离变化为
N, N-2, N-4, N-6...........
这时就会出现歧义, 当N为偶数时,我们知道最终会减为0也就是相遇。
但当N为奇数时我们会发现最后数值会减到-1(也就是比slow指针快了一步)。这时我们会进行第二次比较。
我们假设环的长度为C,这时两指针间距离为N=C-1
这时我们还需要判断N=C-1是否为奇,偶,若为偶数则能相遇,若为奇数则继续进行追击操作,最后的结果还是fast比slow指针多走一步,这时我们可以看到当C-1为奇数时该环陷入死循环(这点有点难理解可以亲手画一画理解一下)。
在这里先总结一下以上的分析:
1.如果N是偶数,第一轮就追上了。
2.如果N是奇数,第一轮追不上,进行第二轮追击
a,如果C-1是偶数,则第二轮就追上 。
b如果C-1是奇数,则陷入死循环。
类比:
如果fast走4步,5步类似推到
进一步探索:
通过上面推导我们知道,快指针永远不会之追上慢指针的条件是:N(快慢距离)为奇数,C-1为奇(也就是C为偶数)时fast指针永远追不上slow指针。
但这种情况真的存在吗?让我们进一步探索。
我们假设没进入环的长度为L,slow指针进入环时与fast相差N,环长为C,当slow指针进入环时fast已经在环中转n圈。
当slow指针刚到环的时候有L+nC+C-N=3L(左边是快指针走的,右边是慢指针3倍)
整理一下得到 2L=(n+1)c-N
我们知道追不上的条件是N为奇数,c为偶数
把此条件带入反证:
偶数 =(n+1)*偶数-奇数
显然等式不成立,所以不存在此条件,一定会追上。
如何找到刚进环结点:
先给出结论:在快反指针相遇的地方定义一个meet结点,在链表头部定义一个head结点,两结点相遇即使带环入口结点。
看到这估计你们会很好奇为什么是这样,那就跟着小编来证明一下吧:
我们假设没乳环部分为L,相遇结点距离入口结点为N,环长为c,快指针转了n圈
快指针走的路程:L+nc+N
慢指针走的路程:L+N
因为我们假设快指针走两步,慢指针走一步。所以有L+nc+N=2(L+N)
整理得到:L=nc-N =》 L=(n-1)c+c-N;
从上述式子可以看出前面(n-1)c是转的圈数,这个不影响的,因为不管转几圈 +c-N也会跟head相遇。所以退出c-N=L
返回入口结点:
首先我们要找到相遇结点—>然后让该结点与head结点同时走,相遇点即是入口结点
有关代码
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* fast=head;
ListNode* slow=head;
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){
ListNode*meet=fast;
while(meet!=head){
meet=meet->next;
head=head->next;
}
return meet;
}
}
return NULL;
}
通过案例: