想想这个问题是来湖大复试的时候老师问的,到现在已经有一年多了,今天才来整理,想想挺惭愧的。
首先链表节点声明如下:
class Node{
int val;
Node next;
public Node(int val){
this.val = val;
}
}
然后我们再来利用尾插法造一个链表:
public void addNodeFail(int val){
Node node = new Node(val);
if(head==null){
head = node;
return;
}
Node temp = head;
while(temp.next!=null){
temp = temp.next;
}
temp.next = node;
}
那么,什么是链表中有环呢?
这样的链表就是有环的,试想,我们搞一个指针一直遍历,是永远不会结束的。
我们先来造个环吧。
//自己造个环
public void CreateCircle(Node head){
Node temp = head;
int i=1;
Node circle = null;
while(temp!=null){
if(i==3){
circle = temp; //保存第3个节点
}
if(i==6){
temp.next = circle; //第6个节点直接就指向刚刚保存的第3个节点上
break;
}
temp = temp.next;
i++;
}
}
这个时候如果调用print()的话,就会一直输出,因为它一直在环内打转转。
解决方法:用两个指针,一个指针一次走一步,另一个指针一次走两步,如果存在环,则这两个指针会在环内相遇
//判断是否有环
public Boolean hasCircle(Node head){
Node slow = head;
Node fast = head;
while(fast!=null&&fast.next!=null){
if(slow==fast) return true; //相遇,存在环
slow = slow.next;
fast = fast.next.next;
}
return false;
}
那么拓展问题来了: (参考来自:http://www.cnblogs.com/ghimtim/p/4882916.html)
1.找环的入口点
假设单链表的总长度为L,头结点到环入口的距离为a,环入口到快慢指针相遇的结点距离为x,环的长度为r,慢指针总共走了s步,则快指针走了2s步。另外,快指针要追上慢指针的话快指针至少要在环里面转了一圈多(假设转了n圈加x的距离),得到以下关系:
s = a + x
2s = a + nr + x
=>a + x = nr
=>a = nr - x
那么,若在头结点和相遇结点分别设一指针,单步前进,第一个指针走过a步,第二个指针走过(nr-x)步,因为a=nr-x,所以它们一定环入口结点相遇。
//找出环的入口
public Node searchEntranceNode(Node head){
Node slow = head;
Node fast = head;
while(fast!=null&&fast.next!=null){
if(slow==fast) break;
slow = slow.next;
fast = fast.next.next;
}
if(fast==null||fast.next==null) return null; //当没有环时返回null
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return slow; //有环时就返回环入口节点
}
2.求环的长度
我们都知道环入口点了,就直接在环入口点设置一个指针,遍历下去,等到下次指针来到环入口点时,看看走了几个节点就是环的长度了。
//求环的长度
public int circleLength(Node head){
Node node = searchEntranceNode(head); //找出环入口结点
if(node==null) return 0; //不存在环的话就直接返回0了
Node temp = node;
int len = 1;
while(node!=temp){
len++;
temp = temp.next;
}
return len;
}