

🏠 个人主页: EXtreme35
📚 个人专栏:
专栏名称 | 专栏主题简述 |
|---|---|
《C语言》 | C语言基础、语法解析与实战应用 |
《数据结构》 | 线性表、树、图等核心数据结构详解 |
《题解思维》 | 算法思路、解题技巧与高效编程实践 |

在链表数据结构中,"环"是一个经典且考察频率极高的话题。这类问题通常分为两个阶段:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。

我们定义两个指针:
slow):一次走 1 步。fast):一次走 2 步。算法流程:
slow 和 fast 都指向头节点 head。fast 和 fast->next 不为空,循环执行: slow 前进 1 步。fast 前进 2 步。fast == slow,说明相遇,链表存在环。fast 遇到 NULL),说明无环。代码实现 (C语言)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head)
{
if (head == NULL || head->next == NULL)
return false;
//快慢指针
struct ListNode *slow = head;
struct ListNode *fast = head;
while (fast != NULL && fast->next != NULL)
{
slow = slow->next; // 慢走1步
fast = fast->next->next; // 快走2步
if (slow == fast)
return true; // 相遇,有环
}
return false; // 走到尽头,无环
}证明(相对速度法):
我们假设在慢指针刚刚进入环的那一时刻,快指针开始追,这时候不知道快指针已经走多少圈了,就假设在此时我图中标记的位置。

下来我们为了证明是否一定能追上,定义几个距离,看看最后是否能推出一个数学表达式。
L。slow 进入环时,fast 追上 slow 的距离为 (沿链表运行方向)。
C。
现在fast速度是slow的两倍,也就是每次多走1,也就是在N这个距离中,每次距离会少1,直至为0,一定会追上。
证明总结:
slow 进入环之后,fast 已经在环内了。slow 进入环时,fast 领先 slow 的距离为 (沿链表运行方向)。
slow 看作静止,那么 fast 相对于 slow 的移动速度是 步/次。
fast 都会把它和 slow 之间的距离缩短 1。。
结论:因为距离每次减 1,必然会减到 0(相遇),绝对不会跳过去。
这是一个非常好的进阶面试题。
分析:
。
。
fast 每次把距离缩短一个相对速度的距离。
这里为什么有这么多情况呢?因为不知道N到底有多大,它有可能是奇数、偶数、0,都有可能。
所以需要对每一个结果进行讨论,当最后距离为0的时候,显然已经追上了,那么-1代表什么意思呢?很显然,代表这时候已经进入下一轮追击了,且快指针就在慢指针前面一个位置。那个-2也是一样的道理。
那我们之前设的圆环长度还一直没用呢,这时候就派上用场了。-1、-2,那这时候相对距离就是C-1、C-2。
走三步的情况下:
为奇数,那么永远追不上。
为偶数,那么下一轮就追上了。
走四步的情况:
走四步就不能看奇偶了,而是是不是三的倍数,因为每次距离会少三,所以三的倍数一定会追上。
N % 3 = 0,首轮就追上。N % 3 = 1,首轮错过,看环长。 (C-1) % 3 = 0 ,下一轮追上。(C-1) % 3 = 1 ,永远追不上。(C-1) % 3 = 2 ,看下述情况。N % 3 = 2 `,首轮错过,看环长。 (C-1) % 3 = 0 ,下一轮追上。(C-1) % 3 = 1 ,看上述情况。(C-1) % 3 = 2 ,永远追不上。那么有没有一个稍微通用的结论呢?尝试一下
slow 进环时,快指针 fast 与 slow 的距离为 Nslow 进环前走的距离为:Lfast 在 slow 进环前已经绕环转了 x 圈距离关系分析
fast 走的总距离为:L + x*C + (C - N)。slow 走的距离为: L。这时候就算是有个半成品的等式了,现在只需要带入速度关系就可,以3倍为例:
关键分析: 如果同时存在以下两个条件:N 是奇数、C 是偶数。
那么根据公式: 偶数 = (x+1)*偶数 - 奇数
逻辑矛盾推导
(x+1)*偶数 的结果一定是偶数结论: 如果步同时存在 N 是奇数 且 C 是偶数 的情况,永远追不上的条件不成立,因此快慢指针一定能相遇。
相遇情况总结
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
代码实现 (C语言)
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow = head;
struct ListNode *fast = head;
// 步骤一:判断是否有环
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
// 步骤二:发现环,寻找入口
// 1. 定义两个指针,index1在头,index2在相遇点
struct ListNode *index1 = head;
struct ListNode *index2 = slow;
// 2. 两人每次都走一步,直到相遇
while (index1 != index2)
{
index1 = index1->next;
index2 = index2->next;
}
// 3. 相遇点即为环入口
return index1;
}
}
return NULL;
}设:
= 头节点到环入口的距离。
= 环的长度。
= 环入口到相遇点的距离(沿运行方向)。
的距离。

推导过程:
slow 走的距离:(注意:通常慢指针在入环第一圈内就会被追上)
fast 走的距离:(
是快指针在环内绕的圈数,且
)
拆解为
:
公式含义解析:
是从头走到入口的距离。
恰好是从相遇点继续往前走,回到环入口的距离。
表示在环里转了
圈(这对最终位置没有影响)。
结论:从头节点出发走
步,和从相遇点出发走
步(实际上是转几圈后走了
),会同时到达环入口。
。
线性相关。
。
slow, fast 等几个指针变量,没有使用额外的数据结构。