142. Linked List Cycle II

这道题我原来是不会的,然后自己思考了一下,发现其实是个数学问题,虽然方法用的是双指针,但是我还是从数学层面进行解释吧。

这道题是在141. Linked List Cycle基础上的变形题,那道题就是用双指针法,一个快指针fast每次走两步,一个慢指针slow每次走一步,如果快指针能和慢指针相遇,则说明存在环。

我们首先看一下有环的情况,例如下面这个链表

这个右边我是故意画成这种环状的,看着比较清晰,其实和原题的链表是一样的。

首先我们必须清楚一点,如果存在环,快指针和慢指针一定是在环中的某个结点位置相遇的,不可能在环之外的结点处相遇。

我们假设环开始的结点下标为i(下标从0开始),对于上图,i=2。
同时,我们设整个链表长度为len,对于上图,len=8。

那么我们很容易知道,环的长度为len-i,我们记环的长度为circle,即circle = len-i。

现在我们假设,当快指针和慢指针相遇的时候,慢指针一共走了n步。现在问题是,走了n步的慢指针,它现在所处结点的下标是多少?

不难算出,走了n步之后,下标为:

Xslow = i+(n-i)%circle

前提当然是n≥i,因为我们刚才说了,快慢指针相遇一定是在环内,所以n≥i必然成立。

慢指针走了n步,快指针则走了2n步,因为快指针速度是慢指针的2倍。

同理,走了2n步的快指针,它所在结点的下标为:

Xfast = i+(2n-i)%circle

现在两者相遇了,意思就是说 Xslow = Xfast

于是有:i+(n-i)%circle = i+(2n-i)%circle,即 (n-i)%circle = (2n-i)%circle。

这就是一个简单的同余方程,有无数个解,解为:
(2n-i) = (n-i) + k*circle, k=1,2,3...

但是注意,我们所说的相遇是第一次相遇,那么这个解就唯一了,即:
(2n-i) = (n-i) + circle

即,n = circle

没想到吧,结论竟是如此的简单!

不信,你看看上面那幅图,图中circle=6(即圈中结点的个数),那么快指针和慢指针第一次相遇时,慢指针一定是走了6步,此时慢指针到了下标为6的位置。

事实上快指针走了2*6=12步,刚好也到下标为6的结点上面了。

现在问题是,我在得到了这个相遇的结点的下标后,我怎么找到环中第一个结点呢?也就是下标为i的结点。

我们不要忘了,第一次相遇,快指针是比慢指针刚好多走了一圈,有人说你怎么知道是刚好多走一圈,因为刚才那个同余方程的解我们是令k=1得到的,k=1就是说多走了一圈。

假设从相遇结点到下标为i的结点需要y步,那么从小标为i结点到相遇结点自然要走circle-y步,于是
2n = i+circle+circle-y
n = i+circle-y

我们消掉其中的n,就会得到
i = y

i=y说明了什么?

说明了在找到相遇位置后,让一个指针从整个链表的头部出发,另外一个指针从相遇位置出发,两个指针每次走一步,当他们相遇的时候,就正好到了i号结点位置,i号结点,就是题目要求我们求的结点。

由于这里我们下标是从0开始编号的,所以head到i号结点的距离(所需要走的步数)也正好是i。

于是我们可以给出如下代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) break;
        }
        if (!fast || !fast->next) return NULL;
        slow = head;
        while (slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return fast;
    }
};
posted @ 2021-01-07 21:03  nullxjx  阅读(109)  评论(0编辑  收藏  举报