尝试证明快慢指针可以相遇问题 以及 证明入环点问题

预热

引用题目:环形链表

快慢指针解法:

class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null) {
        	return false;
        }
        
        ListNode slow = head;
        ListNode fast = head.next;
        boolean flag = false;
        while(slow != fast) {
        	if(fast == null || fast.next == null) {
        		return false;
        	}
        	fast = fast.next.next;
        	slow = slow.next;
        }
        return true;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:39.6 MB, 在所有 Java 提交中击败了31.87%的用户

通过测试用例:20 / 20

问题一:如果有环,快慢指针为什么一定会在环内相遇?

       易知,如果没有环,快慢指针永远不可能相遇。

        提前告知(这里先假设):

  1. 使用链表(不是连续的路程,是离散的结点);
  2. 有环;
  3. 快指针每步走两个结点(当前在结点1,下一步到结点3);
  4. 慢指针每步走一个结点(当前在结点0,下一步到结点1);

        设变量:

  1. 快慢指针起始在同一位置;
  2. 快指针速度为v1;
  3. 慢指针速度为v2;
  4. 圆环结点数(环长)为:N

假设快慢指针在t步后相遇(下面建立方程,求解t是否有解):

v1t % N = v2t % N    ------①

数学

同余定理

        给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/  m 得到一个整数,那么称整数a与b对模m同余,记作a≡b(mod m)。

        其中符号≡ 表示:全等于。

        即:如果,a % m = b % m,则 (a - b) % m = 0,可得a≡b(mod m)。

线性同余方程

        线性同余方程是最基本的同余方程,“线性”表示方程的未知数次数是一次,即形如:
ax≡b (mod n)的方程。此方程有解当且仅当 b 能够被 a 与 n 的最大公约数整除(记作 gcd(a,n) | b)。

        这时,如果 x0 是方程的一个解,那么所有的解可以表示为:

{x0+kn/d|(k∈z)}

        其中 d 是a 与 n 的最大公约数,即d = gcd(a,n)。在模 n 的完全剩余系 {0,1,…,n-1} 中,恰有 d 个解

        使用佩蜀定义来解方程①的一个解:

整除

        若整数b除以非零整数a,商为整数,且余数为零, 我们就说b能被a整除(或说a能整除b),b为被除数,a为除数,即a|b(“|”是整除符号),读作“a整除b”或“b能被a整除”。

        1是任何整数的约数,即对于任何整数a,总有1|a

负数的余数

        负数有余数,余数必须是不超过除数的正整数

        例如:-7÷3=-3……2


有了上面的数学知识,来看方程①,可以变为线性同余方程:

(v1 - v2) t % N = 0 = N % N

        即:                                         (v1 - v2)t ≡ N (mod N)    --------②

        则,(v1 - v2)和N的最大公约数为:gcd((v1 - v2) , N) = gcd(1, N) = 1 = d

        则,  说明方程②有解,且有d=1个解。

        则,由佩蜀定义可得,因为1*1 + 0*N = 1 = d,所以t0 = 1 * N / 1 = N

        即,走了快慢指针走了(t0=N)步后,两个指针会在环内相遇

        则,慢指针走了1*N = N个结点,快指针走了2*N = 2N个结点。

从而可得快慢指针一定可以相遇,并且m+n=N。

同理,当快指针的初始位置在慢指针之前时,如果速度差仍为1时,仍有解,注意写上初始间隔就可以得到线性同余方程了。

如果错误,还望读到此处的大佬提出自己的意见和见解。

练习题1:

来源:环形链表

再看该题就很好理解了~

class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null) {
        	return false;
        }
        
        ListNode slow = head;
        ListNode fast = head.next;
        boolean flag = false;
        while(slow != fast) {
        	if(fast == null || fast.next == null) {
        		return false;
        	}
        	fast = fast.next.next;
        	slow = slow.next;
        }
        return true;
    }
}

问题二:寻找环形链表的入环点问题

         结合问题一中,当快指针每步走两个结点和慢指针走一个结点,可得慢指针走的总结点数为:m+n=N,快指针数走了2N,所以慢指针入环第一圈没走完的时候就会和快指针相遇。

         但是在上面的同等条件下,如何找入环点呢?

         通过问题一我们知道龟兔能够在环内B点相遇,由图,入环点为A,设AB之间为n,龟兔在环内逆时针运动(即由A向B的方向运动),同一个起始位置点为S点,则SA=n,兔的速度是龟的速度的2倍,龟走过的结点数为i,则兔走过的结点数为2i。

        可得:

i = m + n + al;

2i = m + n + bl

        其中a,b分别为龟,兔在环内走了几个完整的圈数。

        做差得:i = (b - a)l;因为b-a为整数,所以i为整数,2i也为整数,表示两者走的总距离 为 环的整数倍。

        所以:

      m + n >= 1*l;

        m % l + n = l;

        表示如果龟兔可以在环内相遇,===> ,则m+n的长度一定为圆环长度整数倍,===>,由问题一得,m+n=1倍环长=l ==> a = 0 ,b = 1

        还表示如果龟兔可以在环内相遇,===>,则如果第二只龟原样从S点出发,第一只龟从B点继续原样逆时针出发,则两只龟走了m步后,在A点相遇。是因为m % l + n = l的原因。

练习题:

来源:环形链表ii

class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null) {
        	return null;
        }
        
        ListNode fast = head;
        ListNode slow = head;
        
        while(fast != null) {
        	slow = slow.next;//慢指针每次走一个结点
        	
        	if(fast.next != null) { //注意条件fast有两个不为null
        		fast = fast.next.next;
        	}else {
        		return null; //没有环
        	}
        	
        	if(fast == slow) { //有环 且 快慢指针在环内相遇
        		ListNode temp = head;
        		while (slow != temp) {
					slow = slow.next;
					temp = temp.next;
				}
        		return temp;
        	}
        }
        
        return null;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:38.6 MB, 在所有 Java 提交中击败了28.14%的用户

通过测试用例:16 / 16

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,使用快慢指针(也称为龟兔赛跑法)是常见的判断链表是否有环的方法。这种方法基于两个指针,一个每次移动一个节点,另一个每次移动两个节点。如果链表中有环,那么快指针最终会追上慢指针;如果没有环,快指针会先到达链表尾部。 下面是基本的实现步骤: 1. 初始化两个指针:`slow`(慢指针)和`fast`(快指针),分别指向链表的头节点。 2. 指针遍历:如果链表不为空,循环执行以下操作: a. `slow`向前移动一步(`slow.next`)。 b. `fast`向前移动两步(`fast.next.next`)。 3. 判断环的存在:如果`fast`指针在某次迭代中到达了`null`,说明链表没有环,因为快指针走过的距离是慢指针的两倍,如果链表长度为偶数,快指针应该在链表末尾找到慢指针,如果奇数,则会先到尾部再回环,不会追上。如果`fast`始终不为`null`,且与`slow`相遇(它们都指向同一个节点),那么链表中存在环。 下面是伪代码形式的实现: ```java public boolean hasCycle(ListNode head) { ListNode slow = head; ListNode fast = head; // 如果链表为空或只有一个节点,不存在环 if (head == null || head.next == null) { return false; } // 快慢指针开始遍历 while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; // 如果快指针先到末尾,则链表无环 if (fast == null) { return false; } // 如果快慢指针相遇,说明链表有环 if (slow == fast) { return true; } } return false; // 如果没有提前结束循环,说明链表无环 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值