文章目录
- 环形链表问题
- 1.环形链表
- 题干
- 思路
- 延申问题
- 总结
- 2. 环形链表 II
- 题干
- 思路
环形链表问题
环形链表就是一个链表没有结束的位置,链表的最后一个节点它会指向链表中的某一个节点形成一个环。
拿力扣的两到题目来看
1.环形链表
题干
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
测试用例1
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
测试用例2
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
思路
快慢指针,如果链表是有环的那么它必然不会结束。定义一个快指针fast和一个慢指针slow,fast一次走2步,slow一次走1步,如果没有环fast就会走到NULL,如果有环fast就会追上slow
代码
bool hasCycle(struct ListNode *head) {if (head == NULL || head->next == NULL){return false;}struct ListNode* fast = head;struct ListNode* slow = head;while (fast != NULL && slow != NULL && slow->next != NULL){fast = fast->next;slow = slow->next->next;if (fast == slow){return true;}}return false;
}
延申问题
-
为什么slow走一部,fast走两步,它们一定会再环里相遇,会不会永远追不上?
不会出现追不上的情况,如果链表又换是一定能追上的。假设slow进环的时候,fast和slow的距离是NNN,紧接着追击的过程中,fast往前走两步,slow走一步,它们每走一次,它们之间的距离都是缩小1的。
NNN
N−1N-1N−1
N−2N-2N−2
.........
222
111
000,距离缩小到0的时候就会相遇。
-
slow走1步,fast走3步?走4步?走n步行不行?
假设slow进环的时候,fast和slow的距离是NNN,接着进行追击的时候,fast向前走3步,slow向前走1步,它们每走一次,它们之间的距离缩小2。那么就会分为两种情况。
假设红色部分是slow入环,fast和slow的距离
1.如果NNN为偶数
假设红色部分距离为10,也就是说距离N=10N = 10N=10,再追击过程中,fast走3步slow走1步,它们的距离是每次缩小2的。
101010
10−210-210−2
10−410-410−4
.........
222
000
如果NNN为偶数,fast走3步slow走一步是可以相遇的
2.如果N为奇数
假设红色部分距离为11,也就是说距离N=11N = 11N=11,再追击过程中,fast走3步slow走1步,它们的距离是每次缩小2的。
111111
11−211-211−2
11−411-411−4
.........
11−1011-1011−10
−1-1−1 ,当距离为−1-1−1的时候fast和slow的差距就变成了C−1C-1C−1,CCC为环的长度。那么这个时候C−1C-1C−1恰好也是奇数,那么fast就永远也追不上slow了
总结
fast走多步,slow走一步时不可行的。
如果slow进环时,上图红色部分的距离也就是fast要追slow的距离NNN为奇数,且环的长度为偶数,那么fast和slow就会一直在环里面打转,永远追不上。
如果距离NNN为奇数,环的长度为奇数,fast走多部则会出现跳过slow的情况,也可能会相遇的。
2. 环形链表 II
题干
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表
测试用例1
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
测试用例2
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
思路
这一道题相较于上一道题的区别就是,一个判断是否有环一个时找到环的入口点。这就是上一个题目的一个变形。
- 设起点道入口点的距离为XXX
- 设相遇点道入口点的距离为YYY
- 设环的大小为CCC
那么慢指针slow走的路程就是X+YX+YX+Y
快指针走的路程比较特殊,如果起点道入口的距离XXX比较长,而环的大小CCC又比较小,那么fast可能会在环里走好几圈,假设走了NNN圈,所以快指针fast走的路程为 X+Y+N∗CX+Y+N*CX+Y+N∗C。
因为快指针fast走的路程是慢指针slow的2倍,所以最后的公式就是
2∗(X+Y)=X+N∗C+Y2*(X+Y) = X+N*C+Y2∗(X+Y)=X+N∗C+Y
化简之后就得出这么一个公式
X=N∗C−YX = N*C-YX=N∗C−Y
假设环比较大,fast刚走完一圈就和slow相遇那么N=1N = 1N=1,起点到入口点的距离就等于环的大小减去入口点到相遇点的距离。X=C−YX = C-YX=C−Y
那么当两个指针相遇时就可以将其中一个指向起点位置,让它们同时走一步,就会在入口点相遇。
代码实现
struct ListNode *detectCycle(struct ListNode *head)
{if (head == NULL || head->next == NULL){return NULL;}struct ListNode* fast = head;struct ListNode* slow = head;while (fast != NULL && fast->next != NULL){fast = fast->next->next;slow = slow->next;if (fast == slow){fast = head;while (fast != slow){fast = fast->next;slow = slow->next;}return fast;}}return NULL;
}
题目来源力扣环形链表