A. 环形链表1
给你一个链表的头节点 head ,判断链表中是否有环.
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况.
如果链表中存在环 ,则返回 true 。 否则,返回 false .
OJ链表
c
// 判断链表是否有环
bool hasCycle(ListNode *head) {
if (head == NULL || head->next == NULL) {
// 空链表或只有一个节点的链表不可能有环
return false;
}
ListNode *slow = head;
ListNode *fast = head->next;
// 快慢指针开始移动,直到它们相遇或快指针到达链表尾部
while (slow != fast) {
// 如果快指针到达链表尾部,说明没有环
if (fast == NULL || fast->next == NULL) {
return false;
}
slow = slow->next; // 慢指针每次移动一个节点
fast = fast->next->next; // 快指针每次移动两个节点
}
// 如果快慢指针相遇,说明有环
return true;
}
【思路】
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。
【扩展问题】
- 为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
- 快指针一次走3步,走4步,...n步行吗?
假设:快指针每次走三步,慢指针每次走一步, 此时快指针肯定先进环。假设慢指针进环的时候,快指针的位置如图所示
此时按照上述方法来环绕移动
当快指针每次走三步,慢指针每次走一步时,它们之间的相对速度差是两步。这意味着快指针相对于慢指针每次都会拉近两个节点的距离。但是,如果环的大小(即环中节点的数量)是快指针和慢指针速度差的倍数(即环的大小是2的倍数),那么快指针可能会"套圈"慢指针而不相遇。具体来说,如果环的大小是2的倍数(比如4、6、8等),那么快指针会在到达环的起点之前追上慢指针,但会在慢指针之后再次到达起点,从而不会相遇。
举个例子,假设环的大小是4个节点,快指针每次走三步,慢指针每次走一步。当慢指针走完一圈回到起点时,快指针已经走了三圈,即12个节点,并回到了起点之后两个节点的位置。因此,它们不会在同一位置相遇。
然而,如果环的大小不是快指针和慢指针速度差的倍数,那么快指针最终还是会与慢指针相遇。这是因为在这种情况下,快指针和慢指针的相对位置会在环中不断发生变化,直到它们在某个节点相遇。
但为什么快指针走两步,慢指针走一步可以确保相遇呢?这是因为无论环的大小是多少,快指针都会在环中追上慢指针。具体来说,假设环的大小为n,那么当慢指针走了x圈时(x为正整数),快指针会走了2x圈。由于它们都在环中移动,所以它们最终会在某个节点相遇。
因此,虽然快指针每次走三步或更多步在理论上是可行的(只要处理好空指针和尾部的情况),但在实践中,快指针每次走两步,慢指针走一步是一种更简单、更可靠的方法来检测链表中的环。
所以只有快指针走两步,慢指针走一步才可以,因为换的最小长度是一,即使套圈了两个也在相同的位置.
B. 环形链表2
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null.
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况.
不允许修改链表。
OJ链表
c
typedef struct ListNode {
int val;
struct ListNode *next;
} ListNode;
// 函数声明
ListNode *detectCycle(ListNode *head);
ListNode *detectCycle(ListNode *head) {
if (!head || !head->next) {
// 空链表或只有一个节点的链表没有环
return NULL;
}
ListNode *slow = head;
ListNode *fast = head;
// 第一步:检测环是否存在
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
// 如果快慢指针相遇,说明有环
if (slow == fast) {
break;
}
}
// 如果fast或fast->next为NULL,说明没有环
if (!fast || !fast->next) {
return NULL;
}
// 第二步:找到环的起始节点
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
// slow(或fast)现在指向环的起始节点
return slow;
}
让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。
证明:
- 定义:
- 假设环的起始节点为 tail(在环中,我们不知道它的确切位置,但它是环的一部分)。 假设从头节点 head 到环的起始节点tail 的距离为 a 个节点。 假设环的长度(即环中节点的数量)为 b 个节点。 当快慢指针相遇时,假设慢指针走了 s步,那么快指针就走了 2s 步(因为快指针每次移动两步).
- 快慢指针相遇时的情况:
- 当快慢指针相遇时,慢指针 slow 已经走了 s 步,其中 s = a + nb(n 是一个非负整数,表示慢指针在环中绕了多少圈)。 同时,快指针 fast 走了 2s 步,即 2s = a + mb(m是另一个非负整数,并且由于快指针走得快,所以 m 会比 n 大).
- 找出 m 和 n 的关系:
- 由于快指针走的是慢指针的两倍,所以 2s = s + kb(其中 k 是慢指针在环中多绕的圈数)。 这意味着 s = kb。 所以,当快慢指针相遇时,慢指针在环中绕了 k 圈.
- 两个指针从不同起点同时移动:
- 现在,我们有一个指针从头节点 head 开始,另一个从快慢指针相遇点开始。 当从头节点开始的指针走了 a步到达环的起始节点 tail 时,从相遇点开始的指针也在环中走了 k*b 步(因为它之前已经走了这么多步)。 由于环的长度是 b,从相遇点开始的指针现在也在环的起始节点 tail。
- 结论:
- 因此,两个指针最终会在环的起始节点 tail 相遇。
伪代码描述:
// 假设 slow 和 fast 已经在环中相遇
slow = head // 重置 slow 到链表头
while (slow != fast) { // 当两个指针没有相遇时
slow = slow->next // 两者同时移动
fast = fast->next
}
// 此时 slow(和 fast)指向环的起始节点
这个逻辑利用了环的性质,确保两个指针最终会在环的起始节点相遇。
那么看到这就接近尾声了哦,劳动节假期也要接近尾声了,呜呜呜,那么咱们下期再见咯