1. 带环链表是什么?
只了解其形但却不了解其内在是远远不够的。接下来,我们将通过例题来进一步了解带环链表。
2. 链表存在问题 (. - 力扣(LeetCode))
题目一:
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
初看这道题是不是一脸懵,不知道该如何下手,你可能会想到要用双指针,然后可能就不知道该怎么做了,有思路,但思路不多,不过,能想到这就已经很不错了。好了,现在来公布一下解题方法吧,那就是需要用到快慢指针,怎么样,是不是和你想到的方法一样呢?没想到也没关系,毕竟你可能是第一次见,这题会了也可以接着看下去,就当巩固一遍了。
好的,废话不多说,我们来看一下解题方法。
具体思路如下:
我们可以先定义两个指针,一颗快指针fast,一个慢指针slow。快指针一次走两步,慢指针一次走一步,这样当快指针进环后,慢指针还在环外。当慢指针进环后,快指针已在环里走了一会了,由于环没有出口,快指针的速度比慢指针的速度快,那么快指针迟早会追上慢指针,一但快指针追上慢指针,即证明有环。
2.1 解法
解题代码如下:
1. class Solution {
2. public:
3. bool hasCycle(ListNode *head) {
4. struct ListNode* fast = head,* slow = head;
5. while(fast && fast->next)
6. {
7. slow = slow->next;
8. fast = fast->next->next;
9. if(fast==slow)
10. {
11. return true;
12. }
13. }
14. return false;
15. }
16. };
不知道聪明的你是否能看懂呢,我想应该是可以的。看不懂也没关系,现在我们来共同看一下这个代码。
首先,定义两个指针变量,一快一慢,这自然不必多说。然后就是下面这个循环体了,思路我们应该都清楚了,下面的代码也很简单,那么就来解释一下这个循环体的条件吧,由于快指针比慢指针走得快,假如这是一个无环的链表,那么理应是快指针先走到链表的末尾,这时候快指针已便历了链表,再走就为空了,这显然不是我们想看到的,所以当fast为空时,就要跳出循环了,又因为·fast一次走两步,故fast->next也不可为空,总结一下,就是二者需同时为真,程序才会运行下去,否则就跳出循环。
2.2 讨论
引:
上面快指针比慢指针一次多走一步,故若链表有环,就一定会相遇。那么如果多走两步,三步,四步呢,n步呢?请证明。是否也一定会相遇呢?请注意,接下来讨论的问题建立在链表有环的基础上。
环长为x | |||
假设slow刚进环时slow和fast的相对位置如图所示,设环长为x,fast还有c长度追上slow。
现在讨论,fast比slow多走两步,可知,当c为偶数时一定可以追上,若c为奇数呢?
那么一定会有个时间节点,fast位于slow前面一个位置,此时相对位移为x-1,若x-1为偶数,也必追上,则x为奇数 ,若x-1为奇数,则永远追不上!
那么是否会存在c为奇数,x为偶数的情况吗?
假设未进环之前的长度为L。
则当slow刚进环时走过路程为L
fast走过路程为L+nx-c(n为fast在环内转的次数)
则可得: 2L = nx-c
则可知:nx-c为偶数,若x为偶数,则c必为偶数,显然不符合条件,故这种条件不存在。
即一定会相遇,无非是时间关系。
3. 环形列表二(. - 力扣(LeetCode))
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
3.1 解法
解题代码如下:
1. class Solution {
2. public:
3. ListNode *detectCycle(ListNode *head) {
4. struct ListNode* fast = head,*slow = head;
5. while(fast&&fast->next)
6. {
7. fast=fast->next->next;
8. slow = slow->next;
9. if(slow == fast)
10. {
11. struct ListNode*meet = slow;
12. while(meet != head)
13. {
14. meet = meet->next;
15. head = head->next;
16. }
17. return meet;
18. }
19. }
20. return NULL;
21. }
22. };
下面照例来解释一下代码。
首先,依旧是判断是否有环,然后就是找到入环的第一个节点。如图所示,设fast和slow相遇时,距第一个入环节点为C,则有如下关系:
2(L+C) = L+C+nx
即L = nx-C
这时将引入另一个指针meet,从头节点开始,当其走L时,slow也已走了有效的剩下路程,这时无论n为何值都不影响。