🔥题目
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null。
3 → 1 → 2 → 4 → 0 → 2
↑ ↓
5 ← 1 ← 6
☘️解析
既然谈到了链表中的环问题,那么咱们就不局限在这一题了。下面将完整的阐述「Floyd判环算法」。
1)判定链表是否有环
slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环。
这很好理解。比如两个人一快一慢,在操场跑步,如果操场是环形的(废话),那么二者一定会相遇。
所以,slow走一步,fast走三步也是可以的。
2)找环的入口
slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点。
这里不好理解,可以画画图,紧跟着下面的逻辑多看几遍。开始吧!
设直线部分长度为m,环部分长度为n,快慢指针在距离入口k处相遇。
fast走的路程:m + An + k = 2s
slow走的路程:m + Bn + k = s
二者相减,得到:(A - B)n = s
又有:s = m + Bn + k
结论是,s是圈长度n的整倍数,又因为s = m + Bn + k,Bn本身就是n的整倍数,所以m + k是圈长度n的整倍数。
接着,slow被拿回起点,fast留在相遇点。
以入点为视角中心,slow距离入点-m,fast距离入点k。经过m时间后,slow位于入点,fast距离入点k+m——而k+m是圈长度n的整倍数,所以此时slow和fast都位于入点!
3)求环的大小
slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小
这一部分没什么技术含量,从入点出发,再回到入点,走的步数自然就是环的大小。
🧊代码
1)判定是否存在环
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (true) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
return true;
}
}
2)找环的入口
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
// slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环
while (true) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
// slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
3)求环的长度
public class Solution {
public ListNode lenOfCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
// slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环
while (true) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
// slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
// slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小
int len = 0;
while (true) {
slow = slow.next;
fast = fast.next.next;
len++;
if (slow == fast) {
break;
}
}
return len;
}
}
🌸补充
Floyd判环算法三步走:
1)slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环。(fast走三步也行)
2)slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点。(哪个放回表头都可以)
3)slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小。(哪个不动哪个走都可以)
最后,你可能会对下面的写法感到奇怪:
// 正确的写法
while (true) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
这是因为,slow和fast开始时就是相同的,如果像下面那样写,while一次都不会执行:
// 错误的写法
while (slow != fast) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}