算法
(链表,快慢指针扫描) $O(n)$
本题的做法比较巧妙。
用两个指针 $first, second$ 分别从起点开始走,$first$ 每次走一步,$second$ 每次走两步。
如果过程中 $second$ 走到null
,则说明不存在环。否则当 $first$ 和 $second$ 相遇后,让 $first$ 返回起点,$second$ 待在原地不动,然后两个指针每次分别走一步,当相遇时,相遇点就是环的入口。
证明:如上图所示,$a$ 是起点,$b$ 是环的入口,$c$ 是两个指针的第一次相遇点,$ab$ 之间的距离是 $x$,$bc$ 之间的距离是 $y$。
则当 $first$ 走到 $b$ 时,由于 $second$ 比 $first$ 多走一倍的路,所以 $second$ 已经从 $b$ 开始在环上走了 $x$ 步,可能多余1圈,距离 $b$ 还差 $y$ 步(这是因为第一次相遇点在 $b$ 之后 $y$ 步,我们让 $first$ 退回 $b$ 点,则 $second$ 会退 $2y$ 步,也就是距离 $b$ 点还差 $y$ 步);所以 $second$ 从 $b$ 点走 $x + y$ 步即可回到 $b$ 点,所以 $second$ 从 $c$ 点开始走,走 $x$ 步即可恰好走到 $b$ 点,同时让 $first$ 从头开始走,走 $x$ 步也恰好可以走到 $b$ 点。所以第二次相遇点就是 $b$ 点。
另外感谢@watay147提供的另一种思路,可以用公式来说明:$a, b, c, x, y$ 的含义同上,我们用 $z$ 表示从 $c$ 点顺时针走到 $b$ 的距离。则第一次相遇时 $second$ 所走的距离是 $x + (y + z) * n + y$, $n$ 表示圈数,同时 $second$ 走过的距离是 $first$ 的两倍,也就是 $2(x + y)$,所以我们有 $x + (y + z) * n + y = 2(x + y)$,所以 $x = (n - 1) \times (y + z) + z$。那么我们让 $second$ 从 $c$ 点开始走,走 $x$ 步,会恰好走到 $b$ 点;让 $first$ 从 $a$ 点开始走,走 $x$ 步,也会走到 $b$ 点。
时间复杂度
$first$ 总共走了 $2x + y$ 步,$second$ 总共走了 $2x + 2y + x$ 步,所以两个指针总共走了 $5x + 3y$ 步。由于当第一次 $first$ 走到 $b$ 点时,$second$ 最多追一圈即可追上 $first$,所以 $y$ 小于环的长度,所以 $x+y$ 小于等于链表总长度。所以总时间复杂度是 $O(n)$。
C++ 代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *entryNodeOfLoop(ListNode *head) {
if (!head || !head->next) return 0;
ListNode *first = head, *second = head;
while (first && second)
{
first = first->next;
second = second->next;
if (second) second = second->next;
else return 0;
if (first == second)
{
first = head;
while (first != second)
{
first = first->next;
second = second->next;
}
return first;
}
}
return 0;
}
};
#### 一种小聪明的的方法
牛逼
66h
思路好呀hhh
吊爆了
可以,面向测试用例编程
牛逼,利用val的范围,相当于将走过的节点标记了,第二次走就会被发现哈哈哈哈
class Solution {
public:
ListNode entryNodeOfLoop(ListNode head) {
if(head==NULL)return head;
vector[HTML_REMOVED] arr(510,0);
auto p=head;
while(p){
arr[p->val]++;
if(arr[p->val]>1){
//cout<<;
break;
}
p=p->next;
}
p->val;
return p;
};
我是你的改进版hhh
### 哈希表真好用,哈哈
就题而论,hash,确实简单
用哈希表确实简单
为什么我用访问数组写,说我超时呢?
求大佬指教!
/
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode next;
* ListNode(int x) : val(x), next(NULL) {}
* };
/
class Solution {
public:
ListNode entryNodeOfLoop(ListNode head) {
vector[HTML_REMOVED]vis(505,-1);
auto p=head;
auto q=head;
while(p&&vis[p->val]==0){
vis[p->val]=1;
p=p->next;
}
};
第一次相遇后,第二次相遇前,first和second的速度变成一样的了?
是的,就是需要一样的,速度要改回来,画个图很清楚的
这样写为什么wrong answer啊?求各位大佬帮忙解释一下
因为你第一个循环都不能进入,刚开始的时候你的定义fast是等于slow的
奥~懂了懂了,昏头了,谢谢大佬指点
class Solution {
public:
ListNode entryNodeOfLoop(ListNode head) {
ListNode *now=head;
while(now!=NULL)
{
if(now->val<0)
{
now->val=-now->val;
return now;
}
else
{
now->val=-now->val;
now=now->next;
}
}
};
//没想到吧
这样写acwing可以过,牛客不可哎
acwing的数据还是稍水了一点
我是这样写的哈:
y总,这一段while循环里面的if条件我觉得应该是second->next,因为如果链表没有环且有偶数个节点,最后second就越界了。
这个代码有什么问题呢
/
Definition for singly-linked list.
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
/
class Solution {
public ListNode entryNodeOfLoop(ListNode head) {
if(head==null||head.next==null) return null;
ListNode first=head;
ListNode second=head;
while(first!=null&&first!=second)
{
first=first.next;
second=second.next;
if(second!=null)
second=second.next;
else return null;
}
first=head;
while(first!=second)
{
first=first.next;
second=second.next;
}
return first;
}
}
while(first!=null&&first!=second)
,上两行不是把first和second都赋值成head了吗改成
while(first!=null && second != null)
突然明白,谢谢大佬,昨天想了一个多小时!
y总,我想问下if (!head || !head->next) return 0;不是保证了链表为空或者只有一个结点的话输出null,那为什么输入[1],0,时,还能输出1呀,不应该是null?
输入[1], 0表示一个包含一个点的自环。输入[1], -1才表示一个长度是1的链表。
可以直接用map,也通过了,代码如下:
可以的,不过map会多用 $O(n)$空间,而且时间也会多一个 $O(logn)$。
爱上这个网站了 如果大佬用的是python就好了
哈哈加油,活动页面里有不少其他同学打卡的python代码~
求助大佬,为什么我的代码的输出结果不对呢?
样例中输出编号为2的结点val 3,我输出了编号为4的val 5
second不是应该走两步嘛,为什么不是second=second->next->next
我是菜鸡请指教
在两个指针第一次相遇以前,second每次走两步;相遇之后,second每次走一步~
懂了,谢谢大佬!
客气啦
这个框框应该咋弄呀
```
代码段
···
这样就可以啦