环形链表的定义
文字解释
单向链表中的特殊的数据结构,不存在尾节点,也无法进行遍历操作,会陷入死循环。
图解
如下图中所示,相当于在我们平常理解的正常的单向链表的尾节点又继续指向链表中的某个节点,这个指向的节点可以是头节点,中间节点,也可以中它本身。
环形链表相关问题
1、如何判断是否为环形链表
判断方法
通过快慢指针的方式解决问题(fast and slow)
slow 走一步,fast 走两步
(至于fast为啥要走2步,下文中有解释)
如果链表中存在环形结构,则 fast 会再次遇见 slow
具体代码如下:
bool hasCycle(struct ListNode *head)
{
struct ListNode *slow = head,*fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;//快慢指针操作
if(slow == fast)
{
return true;//相遇则返回真
}
}
return NULL;
}
但这里存在两个问题就是:
- 这样走fast一定能遇上 slow 吗,是否会错过 slow
- fast 走 N(N>2) 是否能有同样的效果
方法解释
问题1
- 这样走 fast 一定能遇上 slow 吗,是否会错过 slow
结论:一定为相遇。
证明:
第一步:
slow 和 fast,fast 一定是先进环,这时 slow 走了入环前一半的距离
第二步:
随着 slow 进环 fast 已经在环里面走了一段,走多少与环的大小有关,假设 slow 进环时,slow 和 fast 的距离为N
第三步:
slow 进环,fast 开始追 slow ,且他们之间开始的距离为 N , 因为每循环走一次,slow 与 fast 之间的距离就为 N-1,在经过几次循环后 N 就减为0,则 slow 与 fast 一定会相遇。
问题2
- fast走N(N>2)是否能有同样的效果
结论:不一定
证明:
假设 slow 一次走1步,fast 一次走 3 步
slow进环后(因为fast较快可能以及走过一圈或多圈但不影响),fast 和 slow 之间的距离为 N , fast 开始追 slow
则他们之间的距离变化:
N 为偶数时: N——> N - 2 ——> N - 4 ——>……2——>0(可以追上)
N 为奇数时: N——> N - 2 ——> N - 4 ——>……1——>-1(这一次追不上)
如果N为奇数,距离变为-1意味着他们之间的距离变为 C-1 (C为环的周长)
如果 C-1 为奇数,则永远追不上(会陷入死循环)
如果 C-1 为偶数,则可以追上
同理可知N为其他数时能追上的可能性不一定,当N为2时为最好的解决办法。
2、如何确定环形链表入口位置
入口位置即下图中 2 这个结点相对于头节点的位置。
基于上文的证明,先给出以下结论:一个指针从相遇点开始走,一个指针从链表头开始走,他们会在环的入口点相遇。
追上相遇过程中:
慢指针走的距离:L + X
快指针走的距离:L + N * C + X (N>=1)
(N为他们相遇前,fast 在环里面走的圈数)
快指针走的路程为慢指针的2倍
2 * ( L+X )= L + N*C + X
L + X = N * C
L = N *C - X
L = ( N-1 ) * C + (C - X)
标记的位置为从 meetNode 又走到 meetNode
结合上图可证明结论成立
代码:
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *fast = head, *slow = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//相遇
struct ListNode *meetNode = slow;
while(meetNode != head)
{
meetNode = meetNode->next;
head = head->next;
}
return meetNode;
}
}
return NULL;
}