1.题目
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
2.我的题解
2.1 快慢追赶法
先看看在同一个环中快追慢需要的时间(快指针速度2
,慢指针速度1
,追上是指二者重合),分析一下站位:
- 假设二者位置重合,那么已经追上了;
- 假设快指针落后于慢指针一个位置,那么
1
单位时刻后快指针追上慢指针; - 假设快指针落后于慢指针两个位置,那么
2
单位时刻后快指针追上慢指针; - 假设快指针在慢指针前面一个位置,那么
len-1
时刻后快指针追上慢指针,其中len
是环的长度; - 一般地,如果快指针距离慢指针距离为
n
(顺环方向计算距离),那么快指针追上慢指针需要n
单位时间,因为快指针每单位时间可以追上慢指针1
个单位距离; - 显然快慢指针初始站位最大距离小于环的周长,最坏情况下为
len-1
;该情况下快指针追上慢指针时慢指针移动了len-1
单位距离,快指针移动了2*len-2
单位距离; - 主要想得出结论:环中快指针从任意位置开始追任意位置的慢指针,快指针总是可以追上慢指针,且快指针追上慢指针时慢指针移动的距离小于环的长度;
接下来看看在链表中(有环)追赶的情况: - 假设二者从头结点出发,快指针速度为
2
,慢指针速度为1
; - 根据上面的分析,慢指针进入环后,快指针在将来的某个时刻会追上慢指针,且被追上时慢指针移动的距离小于环的长度(重点);
- 假设追上时的情形如下图所示,最终位置将环分成y和z两部分,那么可以得到关系式:
左边是快指针移动的距离,右边是慢指针移动的距离,x+k(y+z)=2(x+y)
k
代表了快指针绕圈圈的圈数,它可能取任意自然数(k=0,1,2,3...)
。由上面的结论可以确定慢指针一圈也没绕就被指针追上了;整理可得
这个公式说明了,x=(k-1)(y+z)+z
x
的距离等于环长的倍数加上z
的长度;即从头结点与相遇的位置以相同的速度前进,将在环的入口处相遇; k=0
时,可以得到x=-y
;这个比较特殊,显然x,y,z
都是正值,于是x=y=0
;也就是说整个链表都是环,头结点(给哪个就是哪个)就是环的入口;- 假如没有环,可以预料到在追赶的过程中快指针会到达空节点,注意判断即可;
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
; - 推导是我自己推导的,但快慢指针的思想来自评论区的大佬们;大佬们太强了,怎么想起来的快慢指针?
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(!pHead)return NULL;
ListNode * fast=pHead,*slow=pHead;
do{
if(!fast || !fast->next)return NULL;
fast=fast->next->next;
slow=slow->next;
}while(fast!=slow);
fast=pHead;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
};
3.别人的题解
3.1 断链法
- 访问完一个节点继续访问下一个节点,且更改当前节点指向空,直到没有下一个节点可以访问;
- 会更改原链表;
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
; - 发现一个小问题,假如链表无环的话,该方法返回最后一个节点而不是返回空节点;但链表中有环的时候,该方法确实可以返回环的入口;
- 虽然本题说了需要判断是否有环,但测试用例似乎全部有环,所以该方法可以AC;
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(!pHead || !pHead->next)return NULL;
ListNode * cur=pHead,*next=cur->next;
while(next){
cur->next=NULL;
cur=next;
next=next->next;
}
return cur;
}
};
3.2 set、map容器
- 遇到节点就加入集合,如果遇到添加节点时发现已经有了该节点,那么该节点就是环的入口;
- 时间复杂度:
O(n)
; - 空间复杂度:
O(n)
;
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(!pHead || !pHead->next)return NULL;
ListNode * cur=pHead;
set<ListNode *> myset;
while(cur){
if(myset.find(cur)!=myset.end())return cur;
myset.insert(cur);
cur=cur->next;
}
return cur;
}
};
4.总结与反思
(1)快慢指针追赶法;
来源:CSDN
作者:永封
链接:https://blog.csdn.net/weixin_43951240/article/details/104196525