预热
引用题目:环形链表
快慢指针解法:
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,下一步到结点3);
- 慢指针每步走一个结点(当前在结点0,下一步到结点1);
设变量:
- 快慢指针起始在同一位置;
- 快指针速度为v1;
- 慢指针速度为v2;
- 圆环结点数(环长)为: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