关于环形链表的一点思考

LeetCode链接:142. 环形链表 II - 力扣(LeetCode)


目录

一、Set

二、快慢指针

0、如果不存在环

1、我们设置了slow和fast两枚指针,其中fast走的比slow快,但是他们一定会相遇吗?

①fast先入环。

②一定会相遇吗?

2、为什么fast速度设置为2,slow速度设置为1?

①保证一定相遇

②一定在slow的第一圈相遇

3、距离关系

4、代码:


一、Set

使用集合看能不能找到之前走过的Node,这是一种取巧的解决方法,在此给出相关代码,但不作为本文分析的重点。

    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            if (set.contains(head)) {
                return head;
            }
            set.add(head);
            head = head.next;
        }
        return null;
    }

二、快慢指针

这是关于这个问题的常规解决思路,我之前在完成这道题的过程中,对于算法是知其然不知其所以然的。这一次重新刷LeetCode的时候,决定把每一个细节都搞懂。让我们从一个一个的基本设定开始吧。

0、如果不存在环

如果不存在环,那么fast一定会先到达null,至此算法结束。

以下讨论基于存在环的情况,对其中的细节问题进行分析。

1、我们设置了slow和fast两枚指针,其中fast走的比slow快,但是他们一定会相遇吗?

我们忽略一切特殊的设定,例如fast的速度是slow的整数倍,仅考虑最基本的情况,V(fast)>V(slow)。

假设fast的速度(每步走的距离)是f,slow的速度是s,假设存在环,其长度为c。

①fast先入环。

基于最基本的设定,fast的速度大于slow,那么当slow到达环的入口时,fast一定已经在环内了。注意,即使此时他们就相遇了,但“环的入口”也属于“环内”。

②一定会相遇吗?

我们考虑一种最普遍的情况,slow入环在某个位置,此时fast已在环内(因为fast先入环),slow在fast“前方”(从它们运动的方向考虑)距离fast的距离是L>0。

使用假设法解决这个问题,假设fast和slow会相遇,意味着方程tf-ts=mc+L有解。其中t是从此时刻开始的时间,取值为正整数,m是相遇时fast多走的圈数,取值为非负整数。

这个方程不一定有解!

考虑以下情况:c=12,L=11,f=6,s=4。在这种情况下,fast和slow在任意时刻的位置都是确定的集合,且两个集合无交集,这意味着它们经过任意圈任意时间,都不可能会相遇。

如下图所示。

2、为什么fast速度设置为2,slow速度设置为1?

我们在上面讨论了,如果我们任意设置fast和slow的速度,仅仅考虑f>s,他们是可能永远不会相遇的。相遇的本质是,fast出现的位置集合和slow出现的位置集合存在交集,然后经过n圈之后,fast和slow在交集处相遇。

所以,为了使fast和slow相遇,从而通过相遇点和他们的速度这些信息,建立环入口和链表起点及相遇点之间的距离关系,我们需要通过设置速度,使得它们一定能相遇且距离关系建立的足够简单清晰。

所以,我们设置了fast速度为2,slow速度为1。

①保证一定相遇

当fast或者slow走的越慢,他们位置的集合越大,从而二者的位置交集就越大,相遇机会就越大。

当f=2且s=1时,二者一定会相遇。

初始时slow领先fast L,每一步之后距离缩短1,经过L步之后二者就一定会相遇。

如果f-s是2或者3或者其他数,由于L%(f-s)不一定为正整数,这就使得可能需要经过若干圈fast和slow才会相遇(不利于距离关系的建立),甚至二者根本不会再相遇。

②一定在slow的第一圈相遇

设置fast速度为2而slow速度为1的另一个好处是保证二者一定在slow进入环的第一圈就相遇,这有助于建立清晰的距离关系。

slow从进入环到绕环一周,需要的时间是c(环的长度);

fast先到环的入口(即slow进入环的地方),再去追击,再到达环的入口的所需时间是(c+L)/2。

1 如果L=c,意味着slow刚进入环时slow和fast就相遇了,显然这是在slow的第一圈相遇

2 如果0<L<c,那么(c+L)/2<c。则fast必定比slow先到达(或直接超过)环的入口,又因为fast与slow必定相遇,则fast必会在slow完成第一圈之前就与其相遇。

最极端的情况,即L取最大值c-1的时候,slow进入环时fast刚好在它前面一步,如上面的图所示。在这种情况下,fast将会在slow第一圈的最后一步(即再走一步就到环入口的位置)追上并与slow相遇。

证明:在这种情况下,slow走c-1步,到达环入口前一步,需要的时间是c-1;fast走L+c-1步,即c-1+c-1=2(c-1)步,需要的时间也是c-1。这意味着,经过相等的时间fast和slow出现在同一位置,即二者相遇,且这个位置在slow的第一圈之内。

至于中间情况,即L<c-1的情况,fast会更早的追上并与slow相遇,因为他们之间的距离更短,这意味着slow能走的距离也更短。

更简单的说明:fast追上slow时一定与其相遇,这个时间是L(因为每一步他们之间的距离缩短1)。而L的最大取值是c-1,即即使fast追上slow花费最长的时间,slow在这段时间里也只能跑到距离入口一步的地方,无法完成一圈!

3、距离关系

head到环的入口的距离是x,环的入口到相遇的地方的距离是y,相遇的地方到环的入口的距离是z

fast走过x+y+n(y+z),slow走过x+y。因为slow没有足够的时间绕环就被追上了。

则x+y+n(y+z) = 2(x+y),x=(n-1)(y+z)+z。

因此,此时让新指针p从head出发,q从相遇点出发,二者都以每次一步的速度走,二者相遇的地方即为环的入口。其中n-1表示q在遇到p之前多绕的圈数。

4、代码:

package LinkedList;

// 13 https://leetcode.cn/problems/linked-list-cycle-ii/description/

/**
 * fast每次走两步,slow每次走一步,如果fast和slow相遇,说明有环,否则没有环
 * <p>
 * 在有环的情况下:
 * head到环的入口的距离是x,环的入口到相遇的地方的距离是y,相遇的地方到环的入口的距离是z
 * 快指针走过的距离是x+y+n(y+z),慢指针走过的距离是x+y,所以x+y+n(y+z) = 2(x+y)
 * 所以x=(n-1)(y+z)+z
 * 此时让新指针p从head出发,q从相遇点出发,二者都以每次一步的速度走,二者相遇的地方即为环的入口
 * 其中n-1表示q在遇到p之前多绕的圈数
 */

public class CircularLinkedListII {
//    public ListNode detectCycle(ListNode head) {
//        Set<ListNode> set = new HashSet<>();
//        while (head != null) {
//            if (set.contains(head)) {
//                return head;
//            }
//            set.add(head);
//            head = head.next;
//        }
//        return null;
//    }

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

        ListNode slow = head, fast = head;
        do {
            if (fast.next == null || fast.next.next == null) {
                return null;
            } else {
                fast = fast.next.next;
                slow = slow.next;
            }
        } while (fast != slow);

        ListNode p = head, q = slow;
        while (p != q) {
            p = p.next;
            q = q.next;
        }
        return p;
    }
}

这样对这个问题就完成了充分的说明,主要的细节是:

1、为什么fast速度设置为2而slow速度设置为1:保证相遇且有利于建立清晰的距离关系

2、为什么一定在slow的第一圈相遇:因为时间不够slow完成一圈就被fast追上了。保证能够在slow的第一圈相遇就是建立清晰的距离关系的原因。

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值