判断单链表是否有环
问题:判断单链表是否有环;若有环,找出进入环的第一个节点;
问题一:判断单链表是否有环
方法一,用一个集合记录以访问过的节点
遍历单链表;将经过的节点都做标记,这里采用的方法是将自己遍历的节点都放入集合Collection visited中,对每个节点都判定是否在集合中出现过,也就是是否曾经访问过。若出现重复访问,则判定有环。
空间复杂度 : O(n)
时间复杂度 : O(n^2)
private static boolean methodSecond(Node head) {
ArrayList visited = new ArrayList();
while(head != null){
if( visited.contains(head)){
return true;
}
visited.add(head);
head = head.next;
}
return false;
}
方法二,快慢指针法:
定义快慢两个指针fast和slow,快指针一次移动两个节点,慢指针一次移动一个节点,从表头开始遍历链表。分为两种情况:
1.如果有环,快慢指针会在环中会和,然后停止遍历。
2.如果没有环,快慢指针会遍历到链表结尾的空节点,然后停止遍历。
问:若有环,为什么快慢指针一定会相遇?
假定有a个环外节点,r个环内节点
当slow指针进入环中的时候,fast指针必定已经在环中了。
此时不妨设,fast指在距离slow指针k个位置。
若slow指针走了x步之后两指针恰好相遇则需要:
存在一个正整数N,使得 (0+x) + N *r= (k+2x);
N=(k+x)/r;
很显然当x等于r-k的的时候,N=1;这时候两者会相遇。
所以slow指针和fast指针必定会相遇。
此时的时间复杂度为O(a+x)
private static boolean thridMethod(Node head) {
Node slow = head;
Node fast = head;
do{
if(fast == null || fast.next==null){
//没有环
return false;
}
slow = slow.next;
fast =fast.next.next;
}while(fast != slow);
return true;
}
问:步长为何取2,能否取3,4,5,6,7?
设步长是len
若slow指针走了x步之后两指针恰好相遇则需要:
存在一个正整数N,使得 (0+x) + N r= (k+ lenx);
N = ((len-1)* x +k) /r;
将对应的步长为len,和k,r带入上式,若存在对应的正整数x与N,则就有能够判定有环。
当len =2 时就会一定存在,对应的一组整数值。
问题二:若有环,找出进入环的第一个节点
方法一,用一个集合记录以访问过的节点
private static Node hasCircle(Node head) {
Collection visited = new ArrayList();
Node node=head;
while(node != null){
if(visited.contains(node)){
return node;
}
((ArrayList) visited).add(node);
node = node.next;
}
return null;
}
方法二,快慢指针法:
环外节点a个,环内节点r个;
如果存在环,则快慢指针会在环内相遇;
环内节点从0开始编号,若快慢指针在x相遇
此时慢指针走了 a+x+nr
此时快指针走了 a+x+mr = 2*(a+x+nr)
a+x = (m-2n)r
将快指针重置为head;和此时slow指向两者重合的编号为x的环内节点
两指针都共同向后移一位,直至两指针再次相等
(此时有slow指针在x处,因为a+x = kr,再走a步,相当于又走了k个圈自然就到了环的第一个节点)
同理此时快指针也走a步,此时fast才会刚刚好进入环中,此时fast与slow才会第二次相遇
两者都指向的节点,即为初始节点。
private static Node secondHasCircle(Node head){
Node slow = head;
Node fast = head;
do {
if(fast==null || fast.next == null){
return null;
}
slow = slow.next;
fast = fast.next.next;
}while(slow != fast);
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}