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

Linked List Cycle

原题链接Linked List Cycle

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

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

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

以图片为例,假设环的长度为 R ,当慢指针walker走到环入口时快指针 runner 的位置如图,且二者之间的距离为 S 。在慢指针进入环后的t时间内,快指针从距离环入口 S 处走了2t个节点,相当于从环入口走了 S+2t 个节点。而此时慢指针从环入口走了t个节点。

假设快慢指针一定可以相遇,那么有 S+2tt=nR ,即 S+t=nR ,如果对于任意的 SRn ,总可以找到一个 t 满足上式,那么就可以说明快慢指针一定可以相遇,满足假设(显然可以找到)

而实际上,由于S<R,所以在慢指针走过一圈之前就可以相遇

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

代码如下

/**
 * 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 walker = head;
        auto runner = head;
        while(runner && runner->next)
        {
            walker = walker->next;
            runner = runner->next->next;
            if(walker == runner)
                return true;
        }
        return false;
    }
};

Linked List Cycle II

原题链接Linked List Cycle II

如果链表有环,寻找环入口位置

以图片为例,假设环入口距离链表头的长度为L,快慢指针相遇的位置为 cross ,且该位置距离环入口的长度为S。考虑快慢指针移动的距离,慢指针走了 L+S ,快指针走了 L+S+nR (这是假设相遇之前快指针已经绕环n圈)。由于快指针的速度是慢指针的两倍,相同时间下快指针走过的路程就是慢指针的两倍,所以有 2(L+S)=L+S+nR ,化简得 L+S=nR

n=1 时,即快指针在相遇之前多走了一圈,即 L+S=R ,也就是 L=RS ,观察图片, L 表示从链表头到环入口的距离,而RS表示从 cross 继续移动到环入口的距离,既然二者是相等的,那么如果采用两个指针,一个从表头出发,一个从 cross 出发,那么它们将同时到达环入口。即二者相等时便是环入口节点

n>1 时,上式为 L=nRS L 仍然表示从链表头到达环入口的距离,而nRS可以看成从 cross 出发移动 nR 步后再倒退 S 步,从cross移动 nR 步后回到 cross 位置,倒退 S <script type="math/tex" id="MathJax-Element-36">S</script>步后是环入口,所以也是同时到达环入口。即二者相等时便是环入口节点

所以寻找环入口的方法就是采用两个指针,一个从表头出发,一个从相遇点出发,一次都只移动一步,当二者相等时便是环入口的位置

代码如下

/**
 * 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;
    }
};

其它的和环有关的题目记得还有

求环的长度

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

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;
        if(walker == runner)
            break;
    }
    int len = 0;
    while(runner && runner->next)
    {
        ++len;
        walker = walker->next;
        runner = runner->next;
        if(walker == runner)
            break;
    }
    return len;
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页