环形链表Ⅱ

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

 

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
    

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head;
        ListNode *fast = head;
        while(fast != NULL){
            fast = fast->next;
            if(fast == NULL){
                return NULL;
            }
            else{
                slow = slow->next;
                fast = fast->next;
                if(slow == fast){
                    ListNode *p = head;
                    ListNode *q = fast;
                    while(p != q){
                        p = p->next;
                        q = q->next;
                    }
                    return p;
                }
            }
        }
        return NULL;
    }
};

 

解题思路:
假设非环部分的长度为F,环部分的长度为C。

快慢指针同时从链表头出发,快指针每次走2步,慢指针每次走1步。

快慢指针最终肯定会在环中某个点相遇,我们设这个点为h点。

 

假设这个环很大,大到慢指针达到入环点的时候,快指针都没有绕环一圈。

那么此时快指针仅在环内走了F个结点,并且假设此时距离入环点还有G个结点。

那么如果慢指针在入环点接着走,走G个结点,那么快指针就走了2G个结点。

因为原本快指针距离入环点只差G个结点,现在走了2G个,那就是过了入环点,然后再走G个点,所以快指针现在距离入环点差的结点数变为F了(环的长度=F+G,走了G就剩F了)。

恰好,头结点到入环点的距离也是F。

所以两个慢指针分别从头结点和现在快指针所在的位置出发,那么两个慢指针相遇的时候,就是入环处了。

 

现在我们再来假设这个环很小,小到他们在h点相遇的时候,快指针已经绕环好多圈了。

假设他们相遇的时候,h点前面有a个结点,h点后面又b个结点,那么慢指针就走过了F+a个结点,而快指针就走过了F+a+k*(a+b)

因为快指针是慢指针速度的两倍,所以2*(F+a) = F + a + k*(a+b)

我们将这个式子化简一下得:F = b + (k-1)*(a+b)

这个式子意味着,如果有两个指针p和q,分别从头结点和h点出发,并且他们速度都是每次走一步。

那么,当p走了F个结点的时候,q会绕环(k-1)圈后回到h点,并且再向前走b个点。这就是这条等式的意义。

然后你就会发现,p和q这时候都会处于入环点。(因为p在头结点出发,走F个点就到入环点;而q从h点出发,走b个点就到入环点。)

也就是说,在环很小的情况下,也只需要设置两个慢指针分别从头结点和h点出发,就能找到入环点了。

 

posted @ 2020-03-11 01:22  jenningszheng  阅读(250)  评论(0编辑  收藏  举报