每天一道LeetCode-----判断链表是否有环,如果有,找到环的入口位置

LeetCode原题链接:

题目链接

思路

1. 判断一个链表是否有环,空间复杂度是O(1)

如果不考虑空间复杂度,可以使用一个map记录走过的节点,当遇到第一个在map中存在的节点时,就说明回到了出发点,即链表有环,同时也找到了环的入口。

不适用额外内存空间的技巧是使用快慢指针,即采用两个指针slow和fast,slow每次移动一步而fast每次移动两步。当slow和fast第一次相遇时,证明链表有环

如果链表中有环,那么当慢指针进入到环时,在未来的某一时刻,快慢指针一定可以相遇,通过这个也就可以判断链表是否有环

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        auto slowPtr = head;
        auto fastPtr = head;
        while(fastPtr && fastPtr->next)
        {
            slowPtr = slowPtr->next;
            fastPtr = fastPtr->next->next;
            if(slowPtr == fastPtr)
                return true;
        }
        return false;
    }
};

2. 找出环的入口点(起点)

当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt回到链表的头部,然后slowPtr和fastPtr各自从自己的位置(fastPtr从两个指针相遇的位置position出发)沿着链表出发,每次步长1,那么当fastPtr和slowPtr再次相遇的时候,就是环路的入口了。

论证过程参考(请点击链接)
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        auto walker = head;
        auto runner = head;
        while(runner && runner->next)
        {
            walker = walker->next;
            runner = runner->next->next;
            if(walker == runner)
                break;
        }
        if(!runner || !runner->next)
            return nullptr;
        auto headWalker = head;
        auto crossWalker = walker;
        while(headWalker != crossWalker)
        {
            headWalker = headWalker->next;
            crossWalker = crossWalker->next;
        }
        return headWalker;
    }
};

3. 衍生问题:求环的长度

当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt不动,fast 绕着环移动,每次移动一步,计数count加1,当两指针再次相遇时,count即是环的大小

第一种方法是利用上面求出的环入口,再走一圈就可以求出长度,代码如下

int cycleLen(ListNode* head)
{
    auto cycleIn = detectCycle(head);
    int len = 1;
    auto walker = cycleIn;
    while(walker->next != cycleIn)
    {
        ++len;
        walker = walker->next;
    }
    return len;
}

第二种方法是当快慢指针相遇时,继续移动直到第二次相遇,此时快指针移动的距离正好比慢指针多一圈,代码如下

int cycleLen(ListNode* head)
{
    auto walker = head;
    auto runner = head;
    while(runner && runner->next)
    {
        walker = walker->next;
        runner = runner->next->next;
        if(walker == runner)
            break;
    }
    int len = 0;
    while(runner && runner->next)
    {
        ++len;
        walker = walker->next;
        runner = runner->next->next;
        if(walker == runner)
            break;
    }
    return len;
}

第三种方法是当快慢指针相遇时,其中一个指针不动,另一个指针每次移动1,当第二次相遇,移动的距离就是环的长度,代码如下

int cycleLen(ListNode* head)
{
    auto walker = head;
    auto runner = head;
    while(runner && runner->next)
    {
        walker = walker->next;
        runner = runner->next->next;
        if(walker == runner)
            break;
    }
    int len = 0;
    while(1)
    {
        ++len;
        walker = walker->next;
        if(walker == runner)
            break;
    }
    return len;
}
posted @ 2021-12-06 20:04  晴天出去走走  阅读(53)  评论(0编辑  收藏  举报