题目是这样:
Given a linked list, return the node where the cycle begins. If there
is no cycle, return null.Note: Do not modify the linked list.
Follow up: Can you solve it without using extra space?
如果链表有环,返回环开始的位置;否则,返回null。
这一题是跟着前面一题(判断链表是否有环)的,这一题也需要判断是否有环,所以也需要用到上一题的思路。上一题的思路大体上是使用两个指针,一个一次走两步,一个一次走一步。如果会相遇,则有环;否则,无环。
再来具体分析这一题。我们用两个指针,pFast和pSlow,一个一次走两步,一个一次走一步。先判断有无环。无环,返回null。如果有环:
我们假设相遇的时候它们走了k步,环的长度为r。那么有2k - k = nr(快的比慢的多走了n个环), k = nr。
假设链表的开始节点与环的开始节点之间的距离为s,环的开始节点与相遇的节点之间距离为m,也就是:
注意图上的r-1。因为r代表的是整个环的长度,图上所示没有包含从尾节点到环开始节点的一小截,所以为r-1。
可以直观得出:s = k - m。
所以s = nr - m。如果我们将它化为s = (n-1)r + (r-m)。这代表什么?如果有个指针从相遇点出发,每次走一步,那么走了r-m部之后,它就停在了环开始节点。再走(n-1)r步,还是停在环开始节点。在它出发的同时,有个节点从链表的开端也开始走,也是每次走一步,走了s步之后,它也是停在了环开始节点。所以这个等式表达的就是这两个节点肯定会在环开始节点相遇。
以下是代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head || !(head->next))
return nullptr;
auto pFast = head, pSlow = head;
while (pFast && pFast->next) {
pFast = pFast->next->next;
pSlow = pSlow->next;
if (pFast == pSlow)
break;
}
if (pFast != pSlow)
return NULL;
auto pSlow2 = head;
while (pSlow != pSlow2) {//attention
pSlow = pSlow->next;
pSlow2 = pSlow2->next;
}
return pSlow;
}
};
想象这样一种情况:环的开始节点也是链表的开始节点。假设快指针和慢指针在除开始节点之外的某一节点相遇,那么之后的pSlow和pSlow2岂不是会在代码中标注attention的地方陷入死循环吗?注意,这种情况不会发生。如果环的开始节点也是链表的开始节点,那么快指针和慢指针一定会在开始节点相遇,而不是其他位置。因为快指针会恰好比慢指针多走一圈,然后又在开始节点相遇。
这题有暴力解法:创造一个存储节点的容器,遇到一个节点,就到容器中找有没有,如果有,说明那就是环的开始,返回它。虽然方法不好,但是如果面试时遇到这题,第一时间把这种思路表述给面试官,也还是不错的。虽然面试官肯定不会满意就是了。。。
然后上面方法的难点我觉得可能在于需要一些巧劲吧,确实不太容易想到这种解法,需要自己画出示意图清晰地表述出来,慢慢分析才行。