许久不见,那么这是最后倒数第三题了,这道题我们来看一下环形链表。
老规矩贴链接:141. 环形链表 - 力扣(LeetCode)
目录

来,大致看一下题目,这道题目需要我们做什么? 需要我们去判断,这个链表内存不存在环,那我们比较的是什么?比较值肯定是不行,那我们发现,能不能在遍历到一定程度以后,让这个节点的指针域等于我们之前遍历过的节点的指针域。那我们发现我们遍历是存储不了数据的。那么思路就卡死了。
**无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。**然而面试的时候经常碰见诸如获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。
当然双指针是一种思维,而不是一个公式。这一点需要读者谨记。
直接从双指针开始讲起会显得很云里雾里,所以我们这里铺垫两道题目来穿插这里的思想。对于链表的长度和偏移量的操作需要慎之又慎。
倒数第k个元素
先来看"倒数第k个元素的问题"。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。可以参考下图来理解:

这里就是固定两人的思想,讲的比较感性一点就像两人推进,前一个人推进结果踩空了之后,那么后一个人就已经知道自己所在位置是对的了。代码放在下面供大家参考。
cs
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *p = head, *q = head; //初始化
while(k--) { //将 p指针移动 k 次
p = p->next;
}
while(p != nullptr) {//同时移动,直到 p == nullptr
p = p->next;
q = q->next;
}
return q;
}
};
获取中间元素的问题。
设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个(可以考虑下如何使其指向后一个结点呢?)

跟上面不一样的是,这里改变的是两个指针的跨度。那么到底怎么让偶数的时候让他指向往后一个节点呢?一种简单的方法是在 fast
无法移动两步时,让 slow
再移动一步。这样,slow
将从中间两个节点的前一个节点移动到后一个节点。 下述代码实现了 n 为偶数时慢指针指向靠后结点。
cpp
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *p = head, *q = head;
while(q != nullptr && q->next != nullptr) {
p = p->next;
q = q->next->next;
}
return p;
}
};
双指针
那我们再回来看我们卡在了哪里。

我们在开头的时候为什么卡住了?因为我们想的是单指针的循环遍历问题,考虑遍历后的节点指针域如何储存,或者是再次读取。
那么根据上一个引题我们来总结快慢指针的特性 ------ 每轮移动之后两者的距离会加一。
下面会继续用该特性解决环的问题。
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。也就是说,套圈了呗!

哈哈,我找了一张很清楚的动图来给大家演示,根据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇!!!
所以这里再重新提出来两个问题:
1.为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可
能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动
一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,
快指针肯定是可以追上慢指针的,即相遇。
2.快指针一次走3步,走4步,...n步行吗?

最后贴一下代码
cs
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *slow = head;
ListNode *fast = head;
while(fast != nullptr) {
fast = fast->next;
if(fast != nullptr) {
fast = fast->next;
}
if(fast == slow) {
return true;
}
slow = slow->next;
}
return nullptr;
}
};
好了,这道题就讲到这里
如果你觉得对你有帮助,可以点赞关注加收藏,感谢您的阅读,我们下一篇文章再见。
一步步来,总会学会的,首先要懂思路,才能有东西写。