经典的三个问题:
1.如何判断是否有环?如果有两个头结点指针,一个走的快,一个走的慢,那么若干步以后,快的指针总会超过慢的指针一圈。
2.如何计算环的长度?第一次相遇(超一圈)时开始计数,第二次相遇时停止计数。
3.如何判断环的入口点:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。来源
一、判断是否有环《采用快慢指针来解决》
最大的问题就是“在快指针遇到慢指针时,慢指针还没有走完一圈”,可以想作快指针肯定比慢指针先进入环,当慢指针进入环的时候,快指针就以每次快1步的速度追慢指针。
一个极端的例子:
环的长短为n,慢指针速度为1,快指针速度为2。当慢指针走完一圈的时候,所花的时间快指针走完了两圈,第一圈是领先到达,第二圈是相遇到达。如果快指针速度越快那么相遇得越早。
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) { // 为了初始化
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) { // 相遇时退出
if (fast == null || fast.next == null) { // 快指针先到达尾部
return false;
}
slow = slow.next; // 走1步
fast = fast.next.next; // 走2步
}
return true;
}
}
二、环的长短
当slow和fast相遇时必然在环内,由于slow每次移动1步,而fast每次移动2步,从此时开始计数,当两者再次相遇时,两者的差值就是一个环的长度。
int cycleLen(ListNode head)
{
if(hasCycle(head))
return 0;
ListNode fast = head;
ListNode slow = head;
int length = 0;
bool begin = false;
bool agian = false;
while( fast != null && fast.next != null) // 防止空指针异常
{
fast = fast.next.next;
slow = slow.next;
//超两圈后停止计数,挑出循环
if(fast == slow && agian == true)
break;
//超一圈后开始计数
if(fast == slow && agian == false)
{
begin = true; // 开始计数的标准
agian = true; // 结束计数的标志
}
//计数
if(begin == true)
++length;
}
return length;
}
三、环的入口
设相遇前的节点数为a,环的节点数为b
第一次相遇:
slow: s = s
fast: f = s + nb = 2s(由于fast每次移动2步)
s = nb就是slow在第一次相遇的时候已经走了nb
只要两者在入口处相遇就可以求得入口。
而s = a + nb就是在入口处,所以只需要slow再移动a步就可以在入口处。而此时不知道a为多少,可以令fast=head,然后slow和fast都每次移动一步,当f=a时,s=a + nb就会第二次在入口处相遇。
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null){
return null;
}
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if (slow == fast){ // 找到了环--判断有环
break; // 此时slow=fast第一次相遇
}
}
// 如果是由于fast遇到null退出循环
if (fast == null || fast.next == null){
return null;
}
fast= head; // 初始化为头结点
while(fast != slow){
fast = fast.next; // 在转圈等待
slow = slow.next;
}
return slow;
}
}