链表中环的入口节点
题目描述:
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next
指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null
。
(中等难度的题)
本题需要解决两个问题:
1.判断链表是否有环
2.找到入环的第一个节点
本题用快慢指针做的结果:
判断是否有环
设定两个指针p,q,从头节点开始
p一次走一步,q一次走两步
当q到达NULL时,说明链表无环
当p、q相遇(快指针追上慢指针)则说明链表有环。
struct ListNode*p = head->next;//慢指针
struct ListNode*q = head->next->next;//快指针
if(p==NULL||q==NULL)//生成成功
return NULL;
while(q!=NULL&&q->next!=NULL)//下面用了二级指针,所以q->next不能为空
{
if(p==q)
break;
p = p->next;
q = q->next->next;
}
if(q==NULL||q->next==NULL)//如果快指针走到空了,说明链表没有环
return NULL;
上面代码初始化指针时已经走了一步了,也可以把p、q都初始都指向head
找到第一个入环的节点
完成判断后,此时的p、q就处在环内的某一节点
下一步的结论是:设置r指向头节点,此时p和r每次各走一步,当p、r相遇时交点就为所求节点。
代码如下:
struct ListNode* r = head;
while(r!=p)
{
r = r->next;
p = p->next;
}
return r;
下面来证明一下这个结论:
p、q相遇,说明q多比p走x*c的距离,x为多走的整数圈数。
则:p走过的距离为m+n, q走过的距离为m+n+c*x
因为p一次走一步,q一次走两步
所以m+n+c*x = 2*(m+n)
m+n = c*x
m = c*x-n+c-c
m = c*(x-1)+c-n
去掉整数圈长度c*(x-1),可得m = c-n;
所以一个指针从头开始走,一个从相遇点开始走,最终会在入环第一个点相遇。
完整代码(C语言)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
if(head==NULL||head->next==NULL)
return NULL;
struct ListNode*p = head->next;
struct ListNode*q = head->next->next;
if(p==NULL||q==NULL)
return NULL;
while(q!=NULL&&q->next!=NULL)
{
if(p==q)
break;
p = p->next;
q = q->next->next;
}
if(q==NULL||q->next==NULL)
return NULL;
struct ListNode* r = head;
while(r!=p)
{
r = r->next;
p = p->next;
}
return r;
}
当然,这题也可以用穷举暴力解
判断是否有环
bool HaveRing(List plist)
{
assert(plist != NULL);
if (plist == NULL)
return false;
if (plist->next == plist)//循环空表
return true;
if (plist->next == NULL)//空表
return false;
Node* q = plist->next;
while (q!=NULL)
{
Node* p = plist;
while (p != q)
{
if (p->next == q->next)
return true;
p = p->next;
}
if (p->next == q->next && p == plist)//环指向头
return true;
q = q->next;
}
return false;
}
穷举的话第一个入环节点也很好找了,但是这样时间复杂度太高,不建议使用。