判断一条链表上是否存在环

判断一条链表上是否存在环

方法一、简单粗暴的方法,两套循环遍历链表,查看是否存在相同的节点,如果有,则存在环;没有,则不存在环,时间复杂度O(n^2),面试官肯定不愿意看到这种解决方式。

是否存在时间复杂度为O(n)的算法求解该问题呢?有的有效的方法(内存开销更少的方法)是由Floyd提出的,所以该方法称为Floyd环判定算法

方法二、该方法使用两个在链表中具有不同移动速度的指针。一旦它们进人环就会相遇,即表示存在环。这个判定方法的正确性在于,快速移动指针和慢速移动指针将会指向同一位置的唯一可能情况,就是整个或者部分链表是一个环。  设想一下,乌龟和兔子在一个轨道上赛跑。如果它们在一个环上赛跑,那么跑得快的兔子将赶上乌龟。下面的图例展示了Floyd算法的过程。

从下图中可以发现,执行最后一步后, 它们将在环中可能并非起点的某一点相遇。


  注意:  龟指针slowPtr每次后移1个结点。兔指针fastPtr每次后移2个结点


/**
 * 链表是否存在环
 * Floyd环判定算法
 *
 * @author zhongling
 * @since 2018年07月26日
 */
public class LinkedListContainsLoop {
    class ListNode{
        ListNode next;
    }
    boolean isLinkedListContainsLoop(ListNode head){
        if(head==null){
            return false;
        }
        ListNode slowPtr=head;
        ListNode fastPtr=head;
        while(slowPtr.next!=null && fastPtr.next.next!=null){
            slowPtr=slowPtr.next;
            fastPtr=fastPtr.next.next;
            if(slowPtr==fastPtr){
                return true;
            }
        }
        return false;
    }
}

衍生问题1------找出环的入口点(起点)

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

下面给出论证过程:(参考博主https://blog.csdn.net/u012534831/article/details/74231581

假设一个链表是下面这样的:

这里写图片描述

ListNode findLinkedListLoopBegin(ListNode head){
        if(head==null){
            return null;
        }
        ListNode slowPtr=head;
        ListNode fastPtr=head;
        boolean isLinkedListContainsLoop=false;
        while(slowPtr.next!=null && fastPtr.next.next!=null){
            slowPtr=slowPtr.next;
            fastPtr=fastPtr.next.next;
            if(slowPtr==fastPtr){
                isLinkedListContainsLoop=true;
                break;
            }
        }
        if(isLinkedListContainsLoop){
            slowPtr=head;
            while(slowPtr==fastPtr){
                slowPtr=slowPtr.next;
                fastPtr=fastPtr.next;
            }
            return slowPtr;
        }
        return null;
    }

设环长为n,非环形部分长度为m,当第一次相遇时显然slow指针行走了 m+A*n+k(A表示slow行走了A圈。附:A*n 是因为如果环够大,则他们的相遇需要经过好几环才相遇)。fast行走了 m+B*n+k。

上面我们说了slow每次行走一步,fast每次行走两步,则在同一时间,fast行走的路程是slow的两倍。假设slow行走的路程为S,则fast行走的路程为2S。

用fast减去slow可得:

S=(B-A)*n

很显然这意味着当slow和fast相遇时他们走过的路程都为圈长的倍数。

接下来,将slow移动到起点位置,如下图:

这里写图片描述

然后每次两个指针都只移动一步,当slow移动了m,即到达了环的起点位置,此时fast总共移动了 2S+m。 考虑到S为环长的倍数,可以理解为:fast先从链表起点出发,经过了m到达环的起点,然后绕着环移动了几圈,最终又到达环的起点,值为2S+m。所以fast最终必定处在环的起点位置。即两者相遇点即为环的起点位置。

衍生问题2,求环的大小(长度)

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

猜你喜欢

转载自blog.csdn.net/qq_35923749/article/details/81218967