带环链表是单向链表里一种,理解如下图
![](https://img-blog.csdnimg.cn/img_convert/e6a9ad9bde3c494aa446361aad2112bd.png)
注意区分环形链表(即首尾相接)
![](https://img-blog.csdnimg.cn/img_convert/31a18300906f4328a68cb9646fff42f0.png)
作为单向链表你常见题型之一的带环链表,考点有难易两种:
给出一个链表判断链表是否带环
譬如力扣题库141题(链接:https://leetcode.cn/problems/linked-list-cycle/)
![](https://img-blog.csdnimg.cn/img_convert/470ee21ef2ab46e6a0d5bdd27c26a5b4.png)
这题较简单,通过快慢指针的做法就能轻松解决,即定义两个结构体指针,一个走一步,一个走两步,当两指针相等时判断为带环,若遍历完整个链表不存在相等情况,就说明该链表不带环,附上代码
bool hasCycle(struct ListNode *head) {
while(head == NULL || head->next == NULL)
{
return false;
}
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)//只判断fast或者fsat->next可能会出现空指针
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)//两指针相遇
{
return true;
}
}
return false;
}
从图形上理解就是(以带环为前提),定义了两个结构体指针,
![](https://img-blog.csdnimg.cn/img_convert/ad1743f1161a42ca8f15ac6d599d643c.png)
两指针同时从起点(头节点)出发,但是fast相比slow多走了一步,也就是说会先到达环处,
![](https://img-blog.csdnimg.cn/img_convert/b09d8c31becb4a37868b36e42db2d0be.png)
当slow到达环时,假设存在(也有可能slow刚好入环,fast刚好到达入环点):
![](https://img-blog.csdnimg.cn/img_convert/30c8435d100d49838c490a0bfcf33219.png)
两指针会在环内一直运动,犹豫fast速度快于slow,这事就可以理解成追击问题,当fast追上slow时(即slow==fast时),循环中止,判断该链表带环。
在这个地方还存在一些问题:
fast一定能追上slow吗?
fast追slow一定不会错过吗?
若slow走一步,fast走三步还能追上吗?
错过一定追不上吗?
假设环内的节点有C个(可以抽象成环的周长为C),slow入环时与fast相距X个节点(在运动方向上相距X)
问题1:
由于fast的速度为2,slow为1。fast相对于slow的速度为1,那么在环内slow与fast的距离会由C-X=>C-X-1=>C-X-2=>...=>C-(C+1)=>0,当相距为0时,slow与fast相遇。因为C,X一定是整数,因而一定能追上。
问题2:
根据问题1的解答可知,因为C,X为整数,距离每次减小1,那么当距离为0时,就会追上且不会错过。
问题3:
根据问题1,fast相对于slow的速度为2,即每运动一次,二者间距离缩小2,对于能否相遇存在几种情况:若C-X为奇数,那么有C-X=C-X-2=>...=>C-(C-1-2)=>1-2=>-1,-1意味着fast跳过了slow,到了slow的前面,即二者错过。若C-X为偶数那么C-X=C-X-2=>...=>C-(C-2)=>2-2=>0,即fast追上slow并相遇。根据分析很容易想到,二者间能否相遇,会不会错过与二者各自的速度没有关系,其取决于二者间的相对速度。
问题4:
根据问题3,当fast超过slow后,设超过的距离为n,那么此时fast与slow相距C-n,通过分析C-n与相对速度的关系(如:奇偶、C-n与相对速度的倍数关系)就可以得出答案。
在1的基础上,返回入环节点
譬如力扣题库142题(链接:https://leetcode.cn/problems/linked-list-cycle-ii/)
![](https://img-blog.csdnimg.cn/img_convert/4e7273c837184ded9e50ad664df3a8e9.png)
(此解析情况下fast走两步,slow走一步)
设:head到入环点的距离为L,slow与fast相遇时相距入环点的距离为X,环的长度为C,如图:
![](https://img-blog.csdnimg.cn/img_convert/1221e9f5e6f84560ac6880e0e6567542.png)
那么就可以表示出两指针的运动距离:
![](https://img-blog.csdnimg.cn/img_convert/086ca46c518b4fca97aafa928f11d941.png)
(nC表示在slow入环前,fast已经在环内运动了n圈。有人可能会好奇:为什么slow不能运动很多圈呢?——很简单,当slow入环时若fast刚好到达入环点那么取得最小相距0;当slow入环时,若fast为入环点+1取得最大相距距离C-1,犹豫fast快于slow,每次运动会让相距距离减一,因而slow最多只能在环内运动C-1,并不能到达一圈)
又fast的速度为slow的两倍,并且二者同时从起点出发,则有
![](https://img-blog.csdnimg.cn/img_convert/5384b0acb30745c0aefc3a360bf2d246.png)
化简得
![](https://img-blog.csdnimg.cn/img_convert/368a6ef178c548dca43997a0396f9eb4.png)
便于理解可以将式子化成
![](https://img-blog.csdnimg.cn/img_convert/dff83ad8528c40de804c6dc6165014a5.png)
根据前面的式子+等式可以知道,(n-1)C+C-X的位置正好为入环点,因此有了一个结论:
一个指针从相遇点走,另一个指针从起点走,二者会在入环点相遇(两指针一次都只走一步)。
附上代码
struct ListNode *detectCycle(struct ListNode *head) {
if(head==NULL || head->next==NULL)
{
return NULL;
}
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow) //L=nC-X
{
//仅仅在判断是否有换的基础上多了一个遍历
//创建一个cur指针从起始位置开始移动
//slow继续移动
struct ListNode* cur = head;
while(cur!=slow)
{
cur=cur->next;
slow=slow->next;
}
return cur;
}
}
return NULL;
}