LeetCode:142. Linked List Cycle II

题目是这样:

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的地方陷入死循环吗?注意,这种情况不会发生。如果环的开始节点也是链表的开始节点,那么快指针和慢指针一定会在开始节点相遇,而不是其他位置。因为快指针会恰好比慢指针多走一圈,然后又在开始节点相遇。

这题有暴力解法:创造一个存储节点的容器,遇到一个节点,就到容器中找有没有,如果有,说明那就是环的开始,返回它。虽然方法不好,但是如果面试时遇到这题,第一时间把这种思路表述给面试官,也还是不错的。虽然面试官肯定不会满意就是了。。。
然后上面方法的难点我觉得可能在于需要一些巧劲吧,确实不太容易想到这种解法,需要自己画出示意图清晰地表述出来,慢慢分析才行。

猜你喜欢

转载自blog.csdn.net/weixin_43462819/article/details/83316071