1.如何判断一个链表是否有环?
-
采用快慢指针的思路,慢指针一次移动一个结点,快指针一次移动两个结点,如果有环,则进入环后,快指针肯定可以追赶上慢指针,这样就会出现两者相等的情况,从而得出有环。
-
如何证明如果有环,进入环后,快指针一定会追上慢指针?
证明:快指针先进入环,慢指针后进入环,假设在慢指针进入环之后与快指针之间的初始相差距离为 K。但是随着快指针与慢指针的移动,它们之间相差距离逐渐为 K , K-1,K-2, K-3…1, 0 当相差距离为 0 时,快指针追上慢指针。
-
代码实现判断一个链表是否有环(例子:Leetcode第141题)
/**
1. Definition for singly-linked list.
2. struct ListNode {
3. int val;
4. struct ListNode *next;
5. };
*/
typedef struct ListNode ListNode;//sp是慢指针,fp是快指针
bool hasCycle(struct ListNode *head)
{
ListNode* sp = head;
ListNode* fp = head;
while(fp != NULL&&fp->next != NULL)
{
sp = sp->next;
fp = fp->next->next;
if(sp == fp)
return true;
}
return false;
}
2.证明链表存在环,则 fast 和 slow 两指针必然会在 slow对链表完成一次遍历之前相遇。
首先引入一个图,方便用来分析:
证明:slow首次在A点进入环路时,fast一定在环中的B点某处。设此时slow距链表头head长为x,B点距A点长度为y,环周长为s。因为 fast 和 slow 的步差为1,每次追上1个单位长度,所以slow前行距离为y的时候,恰好会被fast在M点追上。因为y<s,所以slow尚未完成一次遍历。
3.如果链表存在环,如何求环的入口结点?
再引入一个图,方便用来分析:
- 如果链表存在环,则fast和slow两指针必然会在slow对链表完成一次遍历之前相遇,设此时第一次相遇点为a点,此时 slow 走的距离为 L+X ,fast 走的距离为 L+X+N * C (ps:N为圈数,N>=1)。又因为快指针走的距离是慢指针的 2 倍,所以有:2(L+X)= L+X+NC (N>=1),既可以得到:L=NC-X。若此时再设置一个指向头节点的指针 p ,而slow 在 a 点处,当 p 走了 L 来到 b 点时,a 走了 q=N*C-X,恰好也来到 b 点处,此时,2个指针相遇了。相遇的点即为环的入口结点,如果定义一个 count ,可以算出 L 的长度。
- 代码实现求环的入口结点(例子:Leetcode第142题)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
ListNode* Meet(ListNode* phead)
{
ListNode* sp = phead;
ListNode* fp = phead;
while(fp != NULL&&fp->next != NULL)
{
sp = sp->next;
fp = fp->next->next;
if(sp == fp)
return fp;
}
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head)
{
//快慢指针相遇点到入环点的距离 = 头结点到入环点的距离
ListNode* meetnode = Meet(head);
if(meetnode == NULL)
return NULL;
ListNode* sp = head;
while(sp != meetnode)
{
sp = sp->next;
meetnode = meetnode->next;
}
return sp;
}
4.如何求环的长度
- fast 指针与 slow 指针在第一次相遇后,现在让 fast 不动, 继续让 slow 走。直到 slow 与 fast 再次相遇时,定义的 count 就为 结点数,即为环的长度。