链表结构:
struct List
{
int data;
List* next;
};
(1)判断单链表是否有环
采用追赶法,设置两个指针p和q,从链表表头开始,p每一步走两个节点,q每一步走一个节点,如果链表有环则p和q必相遇。
代码如下
// 判断链表是否有环,时间复杂度O(n),空间复杂度O(1)
List* hasLoopInList( List* head )
{
List *p, *q;
p = q = head;
do
{
if ( p->next != NULL && q->next != NULL )
{
p = p->next->next;
}
else
{
return NULL;
}
q = q->next;
} while ( p != q );
return p;
}
(2)找到环的起始入口(交叉点)
设链表总长度为L,链表头节点到交叉点的距离为x,环长为y,则L=x+y;
设节点p和q共走了s步相遇,由于p每次走两个节点,q每次走一个节点,因此p共走2s个节点,q共走s个节点;
设p与q相遇时,p共绕环走了n圈(n>=1),q绕环第一圈未走完,q和p相遇点距环其实节点为a=s-x;
则p所走总节点数2s满足:2s=ny+x+a=ny+x+(s-x)=ny+s;
由上式进一步得到:ny=s=a+x => a=ny-x;
从而相遇点顺着环的方向再经过r=y-a=个节点到达环的起始入口;
由a=ny-x带入r=y-a,得到r=x-(n-1)y,即x=r+(n-1)y;
因此若设置两个指针,一个从链表头节点开始每一步走一个节点,另一个从相遇点开始每一步走一个节点,则两指针必定在环的起始入口相遇。
根据以上分析,实现找出环起始入口的代码为:
// 如果有环,返回环的起始节点,时间复杂度小于O(n),空间复杂度O(1)
List* findStartNodeOfLoopInList( List* head, List* meetNode )
{
if ( head == NULL || meetNode == NULL ) return NULL;
List* p = head;
List* q = meetNode;
while( p != q )
{
p = p->next;
q = q->next;
}
return p;
}