带环链表是什么🤔?
难道是头上带发光的环吗,
若一个链表带环,那么用指针一直顺着链表遍历,最终会回到某个地方。链表带环不是一个正常的链表,因为他会造成死循环。
判断链表是否有环
思路: 核心思想就是快慢指针,当两个指针相遇的时候说明有环。(为什么会相遇?这个后面会讲到)我们可以定义两个指针(快慢指针),两个指针均从表头开始同时遍历链表,快指针一次走两步,慢指针一次走一步。如果链表带环,那么快慢指针一定会相遇,否则快指针会先遍历完链表(遇到NULL)。😃
例如:龟兔赛跑
struct ListNode {
int val;
struct ListNode *next;
};
bool hasCycle(struct ListNode *head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while (fast && fast->next)// 遍历,如果不带环那么它最后会遇到NULL,循环结束
{
fast = fast->next->next;//每次走两步
slow = slow->next; // 每次走一步
if (fast == slow) // 每走完一次,就进行判断是否相遇了
return true; // 相遇后返回,这链表带环
}
return false; // 遍历完了,fast或fast->next为空,证明链表不带环
}
两个快慢指针的起始位置
快指针进入环的人口,慢指针到环入口还有一半的距离,
当慢指针进换时,快指针开始追击慢指针,它们间追击的距离假设为N,那么每走一次,N就要-1,(N=N+1-2)
所以一直迭代下去
N-1
N-2
N-3
……
0
0就代表快慢指针距离为0,它们相遇了,
小结:
1.如果链表有环,slow每次走一步,fast每次走两步,最后它们一定会相遇,是因为在追击的过程中,距离以整数1的递减,最后为0,0代表距离为0。
2.slow和fast,fast一定是先进环,这是slow走了入环前距离的一半。
3.随着slow进环,fast已经在环里面走了一段,走了多少跟环的大小有关系。
下面我们再延伸一个问题
为什么slow走一步,fast要走两步呢?能不能让fast一次走n步(n>2)?请证明一下 结论:fast一次走n步?
假设slow每次走1步,fast每次走3步,slow到入环点了,fast也在环里走了一会,现在fast开始追slow,追击的距离为N
他们之间的距离变化如下:
N是偶数 - --N是奇数
N ------------- N
N-2 ---------- N-2
N-4 ---------- N-4
N-6 ---------- N-6
…… --------- ……
2 ------------- 1
0 ------------ -1
N是偶数可以追上 N是奇数这一次追不上,
-1意味着他们之间的距离变成了
C-1,(C代表环的长度)
C也有两种情况:
C偶数
偶数-1为奇数
C-1为奇数
C-1相当于N
N-2
N-4
……
-1
接下来C-1永远是奇数
那么慢指针与快指针永远不能相遇
C为奇数
奇数-1为偶数
C-1为偶数
C-1相当于N
N-2
N-4
……
0
当等于0时那么慢指针与快指针相遇
小结:如果N是奇数,距离变成-1意味着他们之间的距离变成C-1 C-1是奇数还是偶数,取决于C的长度 如果C-1是奇数,那么永远追不上了
如果C-1是偶数,那么可以追上
练习题
题目描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。题目来源
思路:
1.先判断链表是否有环,没环返回NULL。
2.通过一个公式(公式将在下面推导)求入环的第一个节点。
公式就是:L的距离就是入环前的距离,
假设链表有环,slow每次走一步与fast每次走两步,假设slow与fast相遇的点为meetNode
公式就是
L(距离)=meetNode到入环第一个结点的距离
如图:
结论:head与meetNode同时走,那么当他们相遇就是入环的第一个节点。
现在我们来推论公式:
假设slow每次走一步,fast每次走一步
当fast追上slow时,
slow走的距离为:L+X
fast走的距离为: L +X+n*c(n>=1,n为fast环的圈数)
因为fast是slow的两倍速度,所以他们的距离也是两倍
2(L+X)=L+X+nc
化简得:
L=nc-X
L=(n-1)*c - c-X
(n-1)*c,不管n是多少meetNode总能回到meetNode
所以
L=c-X
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *slow=head;
struct ListNode *fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
{
struct ListNode *meetNode=slow;struct ListNode *cur=head;
while(meetNode!=cur)// 相等则就是环的第一个结点
{
cur=cur->next;
meetNode=meetNode->next;
}
return meetNode;
}
}
return NULL;
}