判断链表中是否有环
题目描述
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
分析
如果链表中有环,那我们是不是就可以了解到它的尾指针并不指向空,因此也不能进行遍历,会发生死循环。那我们应该怎样去解决这个问题呢?
首先我们可以定义快慢指针(fast,slow)。让快指针一次走两步,慢指针一次走一步。如果有环的话他们两个必定相遇,没环则不会相遇。所以,如果快慢指针相遇就能证明链表有环。
你会不会想问为什么快指针一次走两步,慢指针一次走一步,为什么会在环里相遇呢?
fast会先进环,slow会后进环。假设slow进环时,slow和fast之间的距离为N。slow进环以后,fast开始追击slow,slow每走一步,fast走两步,它们之间的距离缩小一步。它们之间的距离一步一步的缩小。也有可能slow一进环他们两个就相遇了。
N
N-1
N-2
N-3
......
2
1
0 即相遇
所以,slow走一步,fast走两步。只要存在环,他们两个必定相遇。
代码演示
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->next->next;//快指针一次走两步
slow = slow->next;//慢指针一次走一步
//相遇
if (slow == fast)
{
return true;
}
}
//不相遇
return false;
}
扩展问题
快指针一次走三步、四步、五步......N步行吗?
fast会先进环,slow会后进环。假设slow进环时,slow和fast之间的距离为N。slow进环以后,fast开始追击slow,slow每走一步,fast走三步,它们之间的距离缩小2
综上,如果快指针一次走三步,五步等等,可能会相遇,也有可能不会相遇。
返回环的入口点
我们根据上题判断链表是否有环的为基础来进行分析。fast走的路程是slow的两倍。
分析
总结:
一个指针从链表头开始走,一个指针从相遇点开始走,他们会在入口点相遇。
刚好我们要返回入口点。那我们就可以让头指针(head)从链表头开始走,让慢指针(slow)从相遇点开始走,他们两个相遇,便是入口点。
代码演示
struct ListNode {
int val;
struct ListNode* next;
};
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast = head;//快指针
struct ListNode* slow = head;//慢指针
while (fast && fast->next)//循环条件
{
fast = fast->next->next;//一次走两步
slow = slow->next;//一次走一步
//判断是否相遇
if (fast == slow)
{
struct ListNode* meet = slow;//相遇点
//判断入口点
while (head != meet)
{
head = head->next;//head从来链表头一次走一步
meet = meet->next;//相遇点,让meet一次走一步
}
return meet;//返回入口点
}
}
return NULL;//不相遇,返回NULL
}