leetcode-master/problems/0142.环形链表II.md

221 lines
8.6 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
<a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a>
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
</p>
<p align="center"><strong>欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 找到有没有环已经很不容易了,还要让我找到环的入口?
## 142.环形链表II
https://leetcode-cn.com/problems/linked-list-cycle-ii/
题意:
给定一个链表返回链表开始入环的第一个节点。 如果链表无环则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1则在该链表中没有环。
**说明**:不允许修改给定的链表。
![循环链表](https://img-blog.csdnimg.cn/20200816110112704.png)
## 思路
这道题目,不仅考察对链表的操作,而且还需要一些数学运算。
主要考察两知识点:
* 判断链表是否环
* 如果有环,如何找到这个环的入口
### 判断链表是否有环
可以使用快慢指针法, 分别定义 fast 和 slow指针从头结点出发fast指针每次移动两个节点slow指针每次移动一个节点如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点slow走一个节点有环的话一定会在环内相遇呢而不是永远的错开呢
首先第一点: **fast指针一定先进入环中如果fast 指针和slow指针相遇的话一定是在环中相遇这是毋庸置疑的。**
那么来看一下,**为什么fast指针和slow指针一定会相遇呢**
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
![142环形链表1](https://img-blog.csdnimg.cn/20210318162236720.png)
fast和slow各自再走一步 fast和slow就相遇了
这是因为fast是走两步slow是走一步**其实相对于slow来说fast是一个节点一个节点的靠近slow的**所以fast一定可以和slow重合。
动画如下:
![141.环形链表](https://tva1.sinaimg.cn/large/008eGmZEly1goo4xglk9yg30fs0b6u0x.gif)
### 如果有环,如何找到这个环的入口
**此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。**
假设从头结点到环形入口节点 的节点数为x。
环形入口节点到 fast指针与slow指针相遇节点 节点数为y。
从相遇节点 再到环形入口节点节点数为 z。 如图所示:
![142环形链表2](https://img-blog.csdnimg.cn/20210318162938397.png)
那么相遇时:
slow指针走过的节点数为: `x + y`
fast指针走过的节点数` x + y + n (y + z)`n为fast指针在环内走了n圈才遇到slow指针 y+z为 一圈内节点的个数A。
因为fast指针是一步走两个节点slow指针一步走一个节点 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
`(x + y) * 2 = x + y + n (y + z)`
两边消掉一个x+y: `x + y = n (y + z) `
因为要找环形的入口那么要求的是x因为x表示 头结点到 环形入口节点的的距离。
所以要求x 将x单独放在左面`x = n (y + z) - y` ,
再从n(y+z)中提出一个 y+z整理公式之后为如下公式`x = (n - 1) (y + z) + z ` 注意这里n一定是大于等于1的因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例意味着fast指针在环形里转了一圈之后就遇到了 slow指针了。
当 n为1的时候公式就化解为 `x = z`
这就意味着,**从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点**。
也就是在相遇节点处定义一个指针index1在头结点处定一个指针index2。
让index1和index2同时移动每次移动一个节点 那么他们相遇的地方就是 环形入口的节点。
动画如下:
![142.环形链表II求入口](https://tva1.sinaimg.cn/large/008eGmZEly1goo58gauidg30fw0bi4qr.gif)
那么 n如果大于1是什么情况呢就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点只不过index1 指针在环里 多转了(n-1)圈然后再遇到index2相遇点依然是环形的入口节点。
代码如下:
```C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}
};
```
## 补充
在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?**
即文章[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中如下的地方:
![142环形链表5](https://img-blog.csdnimg.cn/20210318165123581.png)
首先slow进环的时候fast一定是先进环来了。
如果slow进环入口fast也在环入口那么把这个环展开成直线就是如下图的样子
![142环形链表3](https://img-blog.csdnimg.cn/2021031816503266.png)
可以看出如果slow 和 fast同时在环入口开始走一定会在环入口3相遇slow走了一圈fast走了两圈。
重点来了slow进环的时候fast一定是在环的任意一个位置如图
![142环形链表4](https://img-blog.csdnimg.cn/2021031816515727.png)
那么fast指针走到环入口3的时候已经走了k + n 个节点slow相应的应该走了(k + n) / 2 个节点。
因为k是小于n的图中可以看出所以(k + n) / 2 一定小于n。
**也就是说slow一定没有走到环入口3而fast已经到环入口3了**。
这说明什么呢?
**在slow开始走的那一环已经和fast相遇了**。
那有同学又说了为什么fast不能跳过去呢 在刚刚已经说过一次了,**fast相对于slow是一次移动一个节点所以不可能跳过去**。
好了这次把为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)的补充。
## 总结
这次可以说把环形链表这道题目的各个细节,完完整整的证明了一遍,说这是全网最详细讲解不为过吧,哈哈。
## 其他语言版本
Java
Python
```python
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 如果相遇
if slow == fast:
p = head
q = slow
while p!=q:
p = p.next
q = q.next
#你也可以return q
return p
return None
```
Go
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
<div align="center"><img src=../pics/公众号.png width=450 alt=> </img></div>