今天我们来讨论一个有趣的链表问题,判断链表是否有环,如果有环,环的入口结点如何寻找?
1. 如何判断一个单链表是否有环呢?
一个单链表如果有环,那它只可能是这样的,如下图所示:
不可能是这样的:
为什么?因为人家是单链表,单链表结点只有一个next指针,一个结点是不可能开出两个分叉的。
那么如何判断链表是否有环呢?
算法1: 依次遍历链表的每一个结点,把遍历的结点都放在一个集合中,在放入集合之前,如果发现当前结点已经存在,那个说明这个链表有环,并且这个结点也是链表的入口结点,两个问题完美搞定。直接贴代码:
public boolean hasCircle(ListNode head){
if(head==null)return false;
Set<ListNode> set = new HashSet<>();
ListNode cur = head;
while (cur!=null){
if(set.contains(cur))return true;
set.add(cur);
cur = cur.next;
}
return false;
}
设链表长度 N N N,那么算法时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( N ) O(N) O(N)。
有没有更好的方法?有的。
算法2: 使用快慢两个指针,slow,fast,快指针依次走两步,慢指针依次走一步,如果链表有环,由于快指针一次走两步,所以快指针先进入环中,如下图所示:
可以得出结论,如果链表有环,则快指针与慢指针一定相遇。一次写出如下代码:
public boolean hasCircle(ListNode head) {
ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
if(slow==fast)return true;
}
return false;
}
算法空间复杂度显然 O ( 1 ) O(1) O(1),当慢指针走到链表入口的时候,假设快指针距离慢指针 x x x步,可知 x x x小于环的长度,因为慢指针每次走一步,快慢指针的距离就缩短一步,所以当慢指针走 x x x步时,快慢指针相遇,由于 x x x小于环的长度,因此,慢指针此时还没有走完一圈。因此时间复杂度 O ( N ) O(N) O(N)
2. 如何找到环的入口结点?
假设慢指针走到入口结点的时候,走的距离为 l e n len len,设环的长度 L L L,当慢指针走到入口结点的时候,快指针此时一定在环中,假设此时快指针已经在环中走了 n n n圈,并多走了 m m m步
因为此时快指针并不一定走了 n n n整圈,当一圈很大的时候, n n n可能为0,当快慢指针在入口相遇的时候, m m m为0,
所以:快指针走的总步数:
l
e
n
+
n
L
+
m
len+nL+m
len+nL+m,由于快指针一次走两步,所以快指针走的步数是慢指针步数的两倍;所以:
2
l
e
n
=
l
e
n
+
n
L
+
m
2len =len+nL+m
2len=len+nL+m,即
l
e
n
=
n
L
+
m
len=nL+m
len=nL+m。
OK,今天的内容就到这里啦,拜拜。