链表闭环判断(Floyd's Tortoise and Hare)

问题

给定一个链表,返回环路开始节点,如果没有则返回空。

直觉

当一个跑得快的跑步者和一个跑得慢的跑步者在有环形的跑道上赛跑会发生什么?在某个时刻,快的跑步者将追上慢的跑步者。

算法

Floyd算法分为两个不同的阶段。第一个阶段是判断在链表中是否有环路,如果不存在就马上返回空,也就不存在第二阶段。第二个阶段就是找到环路的入口。

第一阶段

在这里,我们初始化两个指针-快的hare和慢的tortoise。接下来,hare和tortoise从同一个节点出发(头节点),tortoise每次走一个节点,hare每次走两个节点。如果他们在前进若干步后在同一个节点相遇,我们就返回这个节点,如果最终没有返回节点信息,而是返回空,则代表不存在环路。

证明如图


这里,设环的长度为,非环部分的长度为F,当tortoise指针第一次到达0节点(环入口)的时候,hare指针指向h节点,其中Fh(modC)。由于hare的速度为tortoise的两倍,所以hare的路程为 2F,即在环中走了F  的路程,正好 和 对于模 同余。接下来hare离入口节点的距离为Ch, 那么当tortoise再走 C的路程时,hare正好走了2(Ch) 的路程,而此刻他们都在Ch相遇。

\begin{aligned} h + 2(C-h) &= 2C - h \\ &\equiv C-h \pmod C \end{aligned}

第二阶段

由于第一阶段确定了有环路,第二阶段就是找环的入口节点。接下来初始化连个指针,ptr1:它指向头节点 ptr2:指向第一阶段找到的相遇节点。然后将他们以一次移动一个节点的速度向前移动,直到他们再次相遇为止,并返回这个节点。

看下图来帮助我们理解:

Phase 2 diagram


\begin{aligned} 2 \cdot distance(tortoise) &= distance(hare) \\ 2(F+a) &= F+a+b+a \\ 2F+2a &= F+2a+b \\ F &= b \\ \end{aligned}
  • 时间复杂度 : O(n)

    在环中 hare 和 tortoise 在经过F+C个迭代后相遇,F+ChF+C=n,所以第一个阶段时间复杂度为O(n ,第二阶段走了 F<n  个迭代,所以总的时间复杂度也就是O(n) 。

  • 空间复杂度 : O(1)

代码
public class Solution {
    private ListNode getIntersect(ListNode head) {
        ListNode tortoise = head;
        ListNode hare = head;

        // A fast pointer will either loop around a cycle and meet the slow
        // pointer or reach the `null` at the end of a non-cyclic list.
        while (hare != null && hare.next != null) {
            tortoise = tortoise.next;
            hare = hare.next.next;
            if (tortoise == hare) {
                return tortoise;
            }
        }

        return null;
}

    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }

        // If there is a cycle, the fast/slow pointers will intersect at some
        // node. Otherwise, there is no cycle, so we cannot find an entrance to
        // a cycle.
        ListNode intersect = getIntersect(head);
        if (intersect == null) {
            return null;
        }

        // To find the entrance to the cycle, we have two pointers traverse at
        // the same speed -- one from the front of the list, and the other from
        // the point of intersection.
        ListNode ptr1 = head;
        ListNode ptr2 = intersect;
        while (ptr1 != ptr2) {
            ptr1 = ptr1.next;
            ptr2 = ptr2.next;
        }

        return ptr1;
    }
}


原文地址(https://leetcode.com/articles/linked-list-cycle-ii/#fnref:1)

这篇翻译来自我惨痛的教训,今年11月份去北京某公司面试的时候,面试官问我那门课程学得最好,我迟疑了一下说,数据结构。结果就问了我这个问题,之前没见过想了一下就回答说用辅助空间来解决,面试官说这样可以但是不好,就提示我说可以用两个指针来解决。我又想了一下说,要想他们有差异就要用不同的速度来走,如果有环快的肯定能追上慢的。当他让我写出公式来证明的时候,我没写出来。哎最后,告诉我让我回去多看点书吧。
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值