链表带环问题的方法证明 ——— 数理逻辑推理
引言
在计算机科学中,链表是一种基本的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点都包含数据和一个指向下一个节点的指针。链表的一个重要特性是它可以形成环,即链表中的一个节点通过指针指向之前的节点,从而形成一个闭合的循环结构。
链表带环问题是计算机科学中的一个经典问题,也是在实际开发中经常遇到的挑战之一。这个问题的核心在于判断一个给定的链表中是否存在环,并找到环的起始节点。虽然看起来简单,但要有效地解决这个问题需要一定的技巧和算法知识。
在本文中,我们将深入探讨链表带环问题,包括问题的定义、解决方法和数理逻辑推理。
目录
1. 给定一个链表,判断链表中是否有环。
1.1 力扣题 —— 环形链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode* head) {
//定义快慢指针
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast && fast->next) //fast和fast->都不能为空
{
//快指针一次走两步,慢指针一次走一步。
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return true;
}
}
return false;
}
首先,我们申请两个指针,一个是快指针fast
(一次走两步),一个是慢指针slow
(一次走一步)。
其次,我们让两个指针向前遍历,会有两种情况。
- 链表不成环,那么快指针
fast
就会走向空NULL
。 - 链表如果成环,快指针一次走两步,慢指针一次走一步。那么两个指针的距离会越来越小直至相遇。此时证明了链表成环。
1.2 数理逻辑推理
1.2.1 第一个问题:为什么一定会相遇,有没有可能会错过,永远追不上,请证明。
因为快指针一次走两步,慢指针一次走一步,那么两个指针的距离会越来越小。我们设慢指针slow
入环时两个指针最初距离相差N。快指针每次走两步、慢指针每次走一步:距离就变成了N-1,N-2,N-3,..…,3,2,1,0
当距离相差为0时,就证明两个指针一定会相遇。
1.2.2 第二个问题:slow
一次走一步,fast
一次走3步,走4步,走5步,走n步,一定能追上吗?请证明。
我们设两个指针的距离相差N,圆环的长度为C。
示例:slow
一次走一步,fast
一次走三步。
由此可得:如果同时存在N是奇数且C是偶数,那么fast就永远追不上。
证明
假设slow
进环时,fast
已经在环里转了x圈了,此时fast
跟slow
距离为N。
slow
已经走的距离是:L
fast
已经走的距离是:L + x*C + C-N
进环前的路径+绕环x周+绕环的最后一周中的已走距离
如果fast
每步走的距离是slow
的3倍。
3*L = L + x*C + C-N
2*L = (x+1)*C - N
偶数 = (x+1)*偶数 - 奇数
2*L
肯定是偶数,如果C是偶数的话,那么他只能减偶数才能使2*L
为偶数。但是,我们的假设是N必须为奇数。这是矛盾的,因此假设不成立,并反向证明了,不管fast一次走几步都可以追上。
结论:一定能追上
- N是偶数,第一轮就可以追上。
- N是奇数,第一轮追不上,C-1是偶数第二轮就能追上。
2. 给定一个链表,返回链表开始入环的第一个结点。如果链表无环,则返回NULL
2.1 力扣题——环形链表2
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* slow = head, * fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
//相遇
if (slow == fast)
{
struct ListNode* meet = slow;
while (meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
首先,我们申请两个指针,一个是快指针fast
(一次走两步),一个是慢指针slow
(一次走一步)。
其次,我们让两个指针向前遍历,当两指针相遇时,在相遇点放置一个相遇指针meet
,然后通过循环,meet
指针在相遇点走,head
指针在头节点走,两指针相遇时,此点即为入环的第一个结点。
这时你可能有疑问,为什么一个指针在相遇点走,一个指针在头节点走,两者会在入环的第一个结点相遇。接下来就为大家证明。
2.2 数理逻辑推理
假设slow
进环与fast
相遇时,fast
已经在环里转了x圈了。
slow
已经走的距离是:L + N
fast
已经走的距离是:L + x*C + N
进环前的路径+绕环x周+与慢指针相遇时距入环第一个结点的距离
fast
走的路程是slow
的2倍
2*(L + N) = L + x*C + N
L + N = x*C
L = x*C - N
L = (x-1)*C + C - N
此时x为整数且x >= 1
当x=1时,L = C - N
,由此可得一个指针在相遇点走,一个指针在头节点走,两者会在入环的第一个结点相遇。