一、LeetCode-141. 环形链表
题目
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
struct ListNode *p, *q;
p = q = head;
do {
if(q == NULL || q->next == NULL) { return false; }
p = p->next;
q = q->next->next;
} while (q != p);
return true;
}
};
思路
快慢指针,慢指针走的一定比快指针慢,在循环过程中会出现两种情况:
1、快指针先到达链表终点即后节点为NULL
2、快指针与慢指针重合
所以可以循环判断p是否等于q,等于则说明这是环形链表,否则快指针到NULL就说明非环形链表
这里需要注意的就是p和q位置的选取会影响while的结构
如果p=q=head,用while (p != q) { … }的话,会无法进入循环直接返回true
解决办法有两个:
1、使用do while,先进循环更新位置
2、将p和q进行初始化为一前一后的指针,即p=head,q=head->next
针对第二个解决方案,如果仅仅改p与q的初始化和while,那么可能会导致非法访问指针变量,例如示例:
head = [],pos = -1
此时节点数为0,head = NULL
所以head->next一定会报错
这时只需要在前面最开始加上条件判断排除这种情况即可(官解)
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
二、LeetCode-142. 环形链表 II
题目
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
struct ListNode *fast, *slow;
fast = slow = head;
do {
if(fast == NULL || fast->next == NULL) { return NULL; }
slow = slow->next;
fast = fast->next->next;
if(fast == slow) {
while(head != slow){
head = head->next;
slow = slow->next;
}
}
} while(slow != head);
return head;
}
};
思路
在第一题的思路上,我们首先得判断链表是否为环形链表,这里就不重复说明了
下图为假设第一次相遇情况:
假设:
a为head到环形链表头节点的距离
b为环形链表头节点到第一次fast与slow相遇的距离
c为fast与slow第一次相遇的距离到头节点的距离
分析fast与slow的步长,如图设fast与slow走的长度分别为f和s,第一次相遇前fast走过的圈数为k
则我们可以写出以下等式:
s = a + b
f = a+ b + k(b + c)
又易知 f = 2s,又可以写成 2(a + b) = a + b + k(b + c)
我们简单的变化一下:
2
(
a
+
b
)
=
a
+
b
+
k
(
b
+
c
)
2(a + b) = a + b + k(b + c)
2(a+b)=a+b+k(b+c)
= > 2 a + 2 b = a + b + b + c + ( k − 1 ) ( b + c ) => 2a + 2b = a + b + b + c + (k - 1)(b + c) =>2a+2b=a+b+b+c+(k−1)(b+c)
= > a − c = ( k − 1 ) ( b + c ) => a - c = (k - 1)(b + c) =>a−c=(k−1)(b+c)
看最后化简的结果
(b + c)为环形的长度
在第一次相遇后:
head的位置还是在头节点处,距离环形链表头节点的长度为a
slow的位置距离环形链表头节点的长度为c
(a - c)为 head走了c步后,距离环形头节点的长度刚好为长度的整数倍,也就是刚好在环的头
而slow走c步后,也刚好在环的头
最后slow会与head相遇在环的头节点:
总结
最后我们只需要两步走:
- 找出第一次fast与slow相遇时的位置
- fast停止移动,head开始移动,找出head与slow开始相遇的位置
复杂度分析
首先有个问题:为什么slow在第一圈就会与fast相遇?
用两个极限思考,设slow此时在头节点处
假设一:fast刚好处于slow后一个节点的位置(最慢相遇)
那么此时fast与slow相遇时slow会处在环的头节点的前一个位置,走的路程为:b + c - 1 < b + c
假设二:fast刚好处于slow前一个节点的位置(最快相遇)
那么此时fast与slow相遇时slow仅移动一步就相遇,走的路程为:1 < b + c
总结
时间复杂度为O(n),空间复杂度为O(1)