1 题目介绍
给你一个链表的头节点
head
,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回
true
。 否则,返回false
。
示例1:
输入以下这条链,head指向值为3的节点
返回值应该为 true
示例2:
输入以下这条链,head指向值为1的节点
返回值应该为 true
示例3:
输入以下这条链,head指向值为1的节点
返回值应该为false
这里放上链接. - 力扣(LeetCode)
2 解题思路
2.1 思路1(逆序法):
(注意:此方法不是最优解)
基本思想就是逆序整个链表时(简单来说就是让箭头掉头指回上一个节点),如果这个链表是带环的,那么指针cur会从头节点开始遍历,最终回到头节点,若链表不带环,则指针cur回不到头节点
由于有些链表不带头节点,所以一开始需要我们手动创建一个哨兵位
我们需要先定义三个指针,负责将箭头转向
接着就是如法炮制
注意,这里nextnode回到了值为2的节点
(注意:nextnode指向NULL时,让循环停止,并且开始检测 cur 是否回到了哨兵位,否则就停不下来了)
此时我们发现 cur 回到了哨兵位,一旦产生这种情况,就证明这个链表是带环的
立马返回 ture 就可以
对于不带环的链表来讲,检测到 cur 没有回到哨兵位,就返回 false
2.2 思路2(快慢指针):
基本思路就是快慢指针,一个指针跑得快一点,另一个慢一点,让他俩一直随着 next 指针遍历,快指针和慢指针的距离不断缩短,直到两者相遇,就好比两个人绕操场跑步,都是匀速跑,一个块一个慢,如果不记时间的话那么,这两个人一定会相遇,一旦相遇,就说明这个链表是带环的,因为两者速度不一样,跑道是直线的话是一定不会相遇的
(这个方法是最优解)
快指针:一次走两步(每次走一步都要判断一下 fast 和 fast->next 是否为 NULL,防止出现野指针)
慢指针:一次走一步
此时发现两者相遇,返回 true
如果 fast 或者 fast->next 在某个时刻 为 NULL,证明这个链表不是带环的,返回 false
3 实现代码
3.1 思路1(逆序法):
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{
//输入为空,返回false
if(head == NULL)
{
return false;
}
//创建头节点
ListNode* headnode = (ListNode*)calloc(1, sizeof(ListNode));
if(headnode == NULL)
{
perror("hasCycle::calloc");
exit(1);
}
headnode->next = head;
//创建cur,前节点,后节点
ListNode* lastnode = NULL;
ListNode* cur = headnode;
ListNode* nextnode = headnode->next;
//执行逆置
while(nextnode != NULL)
{
cur->next = lastnode;
lastnode = cur;
cur = nextnode;
nextnode = nextnode->next;
}
//判断是否是最开始的那个头节点
if(cur == headnode)
{
return true;
}
else
{
return false;
}
}
3.2 思路2(快慢指针):
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{
//如果输入为空,或者只有一个节点并且其next也没有指向任何节点
//就判断为非带环节点
if(head == NULL || head->next == NULL)
{
return false;
}
//定义快慢指针
ListNode* fast = head;
ListNode* slow = head;
//快指针碰到NULL就停下
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
//如果两者相遇,就一定是带环链表
if(fast == slow)
{
return true;
}
}
//一旦快指针被迫停下,就证明这个链表一定不带环
return false;
}
4 衍生思考
4.1 带环链表里快慢指针为什么一定会相遇
当 slow 进入环的时候,slow 和 fast 就会有距离差了,每一回合之后,fast 和 slow 就会缩短一个节点的距离,直到 fast 和 slow 的距离为0,即相遇为止
上图这里,fast 和 slow 就相差了10个节点
上图这里接着走了一回合,fast 和 slow 的距离就缩短了1变成了9个节点了
所以两者在环里面一定会相遇
4.2 快指针一次跳更多步,两者还会相遇嘛
比方说,快指针一次跳3步,那么每回合两者距离缩短的速度是2,也就是每回合两者距离缩短2,如果两者相距的距离是2的倍数,那么 fast 就会和 slow 相遇,如果二者相距的距离不是2的倍数,即是个奇数,那么快指针一定会在某个回合直接超过 slow 而不会相遇,而此时,你会发现正是因为 fast 越过了 slow,导致两者相距的距离为“奇数 - 1”即偶数了,那此时 fast 和 slow 的相遇就是必定的事了,如果快指针一次跳更多也是一样的道理,如法炮制即可
偶数情况:
奇数情况:
注意此时这里两者相距 7
上图这里两者就相距 10 为偶数