原题链接:https://leetcode.cn/problems/linked-list-cycle/
题目描述:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
带环链表类似于以下结构:
解题思路:因为该题涉及部分数学知识,所以我们先讲如何做,再讲为什么。首先我们定义快慢指针,都指向头结点,让快指针每次走两步,慢指针每次走一步,如此不断迭代。如果最后快指针为NULL了,那么链表就是不带环的;如果slow最后追上了fast,那么链表就是带环的了。
为什么可以这么做?
其实可以把这里抽象成一个追及问题。因为链表是带环的,快指针又比慢指针走的快,所以它肯定会进入循环当中,等到慢指针也进入循环中后,他们再不断迭代后肯定会相遇了。
可能有人觉得不太对,如果数值巧一点,会不会一直都不会相遇呢?
这里先给出答案,要看快慢指针的差值,如果快指针每次走的步数—慢指针每次走的步数=1时,它们就一定能相遇,否则就要看具体的链表个数数值了。
推导过程:
我们先将该问题抽象成如下模型
当慢指针入环时,设此时快慢指针距离为N,如下
上文说了将其抽象成追及问题,当快指针每次走的步数—慢指针每次走的步数=1时,每迭代一次快慢指针之间的距离就减一,不断减到N=0时就相遇了。
如果快指针每次走的步数—慢指针每次走的步数!=1时
我们记此时慢指针slow与快指针fast的距离为N,慢指针每次走一步,快指针每次走三步,因此N每次减少2,于是就有:
N - 2
N - 4
N - 6
N - 8
.....
最终有两种情况:
N = 1,然后再减2 -> -1
N = 2,然后再减2 -> 0
我们假设环的长度为c,那么此时再次追击两个指针的距离就为:C - 1
,令L = C - 1
,则:
L - 2
L - 4
L - 6
L - 8
.....
最终有两种情况:
L = 1,然后再减2 -> -1
L = 2,然后再减2 -> 0
因此,这里又有两种情况,有了前面的推导,这里不难得出,当C为偶数时,则L为奇数,此时继续追不上,并且下一次也是一样,所以这里会陷入死循环;当C为奇数时,则L为偶数,此时是追的上的。因此,当C为偶数时,永远追不上,当C为奇数时,追的上。
所以,通过以上分析,快指针每次走三步,慢指针每次走一步不一定能判断是否为带环链表,可能会陷入死循环,尽管陷入死循环就说明带环。
以此类推,其他情况也可以按此思路推出来,不知道大家是否学会了呢?
代码实现
bool hasCycle(struct ListNode *head) {
struct ListNode* fast = head, * slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow) return true;
}
return false;
}
原题链接:https://leetcode.cn/problems/linked-list-cycle-ii/
题目描述:给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。注意:不允许修改链表。
注意:与上题不同,本题不仅需要判断是否带环,还需要返回链表入环的第一个结点。
解题思路:
还是先说做法再说思路
先以第一题的思路用快慢指针找到快指针和慢指针相遇的那个点,然后一个指针从该点开始走,一个指针从头开始走,两个指针每次走一步,最终两个指针会在入环的第一个节点相遇,然后返回这个节点。
推导:
假设头结点到入环点的距离为X,入环点到相遇点的距离为N,环的长度为C。设相遇点为pos
由此,我们可以计算一下当快慢指针相遇时,两指针各自走的路程。
快指针:
X + nC + N
;慢指针:
X + N
;
为什么是nC呢,因为如果环很小的话,当慢指针入环后,快指针会已经转了很多圈了。
而慢指针没有C
是因为:慢指针入环后最多只会走C - 1
步,不可能出现在环内步数超过一圈的情况,因此慢指针没有C
因为快指针每次走两步,是慢指针的两倍,所以路程也是慢指针的两倍
所以可得:X + nC + N = 2(X + N)——>,令C-N=L,X = (n - 1)C + L;
这个表达式的意思就是头结点到入环点的距离等于相遇点绕了n-1圈后再走L,因为是环所以其实c可以不用看,X=L,所以从pos位置和头结点开始每个指针各走一步,他们就会在入环点相遇。
代码实现:
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *slow = head, *fast = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
// 如果找到相遇的那个点,就开始找入环的第一个节点
if (slow == fast)
{
struct ListNode *cur = slow;
// 一个从头开始走,一个从当前节点开始走
while (cur != head)
{
head = head->next;
cur = cur->next;
}
// 最终相遇的那个点就是入环的第一个节点
return cur;
}
}
// 如果链表不带环,就返回NULL
return NULL;
}