这篇文章主要讨论

leetcode 141
leetcode 142

首先,问题141和142都是不保证链表一定有环路,因此我们首先需要判断链表是否有环路。其次对于142,还要判断链表环路开始。

双指针判断是否有环路

设置一个快指fast针和一个慢指针slow,初始都指向head。后面fast每次移动两步,slow每次移动一步。加如链表有环,在某一个时间,fast一定会追上slow,和slow重合,且绝对不会跳过slow。因为fast的速度为2slow速度为1,以slow为参考系,因此fast相对于slow的速度为fast-slow=1fast相对slow每次只向前一步,因此不会跳过slow

因此141的答案可以写成这样,要注意fast前进时,需要分别判断fast.Next,fast.Next.Next是否为空。然后因为fast快于slow,因此fast探路完了不需要再判断slow

func hasCycle(head *ListNode) bool {
    if head==nil{
        return false
    }
    started:=false
    fast,slow:=head,head
    for !started || fast!=slow {
        started=true
        if fast.Next==nil || fast.Next.Next==nil{
            return false
        }else{
            fast=fast.Next.Next
        }
        slow=slow.Next
    }
    return true
}

判断环路起始点

这里借用官方图,链表头head到入环点距离为ab+c为一个完整的环长,fast将在入环后转了n圈后与slow相撞。

假设fast在环里转了n圈后和slow相撞,fast走过的路程可以表示为f=a+n(b+c)+b。此外,这里先说明一个事实,slow一定会在入环后第一圈内和fast相撞,这里我们先不证明,后面再解释。基于这个事实,我们可以得出slow走过的路程为s=a+b。此外,我们知道fastslow从一个起点出发,fast速度是slow的两倍,因此,fastslow相撞时,fast的路程一定是slow的两倍,即f=2s

基于上述公司,开始推理。因为我想知道链表头起始点,我们要关注a

2s=f
2(a+b)=a+n(b+c)+b
a=n(b+c)-b
a=(n-1)(b+c)+c

因此可以得出,链表头移动到入环点的距离a等于在环内转了n-1圈后再走了距离c。这个可能难以理解,但是我们知道走完一次b+c相当于转了一圈,也就是停留在原地,因此我们可以把(n-1)(b+c)约掉。即变成a=c。或者可以这样想,fast如果只转了一圈后就撞上slow,即n=1,那可得a=c

好,现在我们只要求出c就行了。因为相撞点到入环点的距离等于链表头到入环点的距离。所以我设置一个指针p,从链表头开始,和slow每次移动一格,最后一定会在入环点相撞。

因此142的代码

func detectCycle(head *ListNode) *ListNode {
    if head==nil{
        return nil
    }
    started:=false
    fast,slow:=head,head
    for !started || fast!=slow {
        started=true
        if fast.Next==nil || fast.Next.Next==nil{
            return nil
        }else{
            fast=fast.Next.Next
        }
        slow=slow.Next
    }
    
    p:=head
    for p!=slow{
        slow=slow.Next
        p=p.Next
    }
    return p
}

为什么slow一定会在第一圈内相撞

其实这个想法是官解评论区看到的,我们可以通过转换参考系的办法来思考。已知fast速度为2slow速度为1,因此fast相对于slow的速度为fast-slow=1。上文也说过,这也是为什么fast一定会撞上slow而不会跳过。

假设slow没有在进入环的第一时间和fast相撞,那此时在环内,fastslow一定相隔一段距离。这是一段废话,因为不相撞肯定就相隔距离。假设环周长为d,那么,fastslow的最大距离只能为d-1

这个时候,我们转换参考系,以slow为参考系。根据上文的结论,slowfast最多相隔d-1距离,而且fastslow相对速度为1。将根据匀速运动公式时间=路程/速度,得出slow最多可以移动的时间为(d-1)/1,即d-1。这时把参考系转换为整个链表,已知slow速度为1,可以走的时间为d-1,即可以走d-1次。还是根据匀速运动公式路程=时间*速度。所以能走的路程为(d-1)*1d-1,也就是走不完一个完整环长就会和fast相撞。


rwxe
91 声望5 粉丝

no tengo trabajo.