判断链表是否有环的三种方法
1. 在节点ListNode中增加一个域,用于记录此节点是否已经被访问,如下ListNode中被注释掉代码。此方法简单,能找出环开始的节点,但是增加了链表的开销。如果链表非常大 则需要十分大的内存来保存此域。
2. 使用链表逆序的方法。从头结点开始一直将链表逆序,头结点的next域为空。如果有环的话链表逆序必定会回到头节点而终止。这种做法同样简单,确保O(n)时间终止算法。但是修改了链表结构。原链表已经被改变。但是不能找出环开始的节点
3. 使用两个变量赛跑,一个变量走两步/次,一个变量走一步/次。 如果有环则两个变量必定会相逢,其中快的变量比慢的变量多走一圈。此算法 如果链表的环非常大 则需要较大的遍历时间。此算法同时也能找出环开始的地方
class ListNode {
int val;
ListNode next;
// Boolean visited=false;
ListNode(int x) {
val = x;
next = null;
}
}
方法一、
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null)
return false;
while(head!=null){
if(head.visited==false)
head.visited=true;
else
return true; //同时第一个vistited为true的节点即为环开始的节点。
head=head.next;
}
return false;
}
}
方法二、
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null)
return false;
ListNode p = head;
ListNode q = head.next;
ListNode k = head.next.next;
while (k != null) {
if (p == k || k == head)
return true;
if (p == head)
p.next = null;
q.next = p;
p = q;
q = k;
k = k.next;
}
return false;
}
}
return false;
ListNode p = head;
ListNode q = head.next;
ListNode k = head.next.next;
while (k != null) {
if (p == k || k == head)
return true;
if (p == head)
p.next = null;
q.next = p;
p = q;
q = k;
k = k.next;
}
return false;
}
}
方法三、
/*找出环开始的节点证明:设链表长度为N(每个节点访问一次) 链表头到达环开始节点长度为 s ,环的大小为S因为 快节点比慢节点多跑一圈 到达相遇节点, 设n为循环的次数。 所以有 2*n-n=S =》 n=S,即到达相遇节点的时候慢节点相当于刚好走了一个环的大小。所以慢节点走完链表N剩下的节点为N-S。而从头节点到环开始的距离s =N-S。所以从头结点开始和慢节点同时再走N-S步即能在环开始的地方相遇。*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode meetNode = fineNode(head);
if (meetNode == null) //有相遇的节点就说明有环,没有就没有环
return null;
while (meetNode != head) {
head = head.next;
meetNode = meetNode.next;
}
return meetNode;
}
public static ListNode fineNode(ListNode head) {
if (head == null)
return null;
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) { //两个变量赛跑,找出相遇的节点
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return slow;
}
}
return null;
}
}