题目:
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?
本题与之前的Linked List Cycle差不多,上道题目是判断一个链表是否含有环,这个题目是找到环的开始位置,首先我们不考虑额外的空间要求,那么最简单的方法就是使用Hash表来实现,保存在此之前出现的每个节点,这样只需要判断后来的节点是否已经出现过即可。代码入小所示:
public ListNode detectCycle(ListNode head) {
HashSet<ListNode> tmp = new HashSet<>();
while(head != null){
if(tmp.contains(head))
return head;
tmp.add(head);
head = head.next;
}
return null;
}
这种方法使用了额外的存储空间,而且效率很低,那么我们想想该如何改进呢。先考虑暴力破解法,就是每次遍历一个节点时我们就判断该节点是否在之前出现过,这样使用两次循环来实现,但是由于重复判断次数太多,会导致超时==代码入下所示:
public ListNode detectCycle1 (ListNode head){
ListNode cur=head, newHead=head;
int i = 0;
while(cur != null){
for(int j=0; j<i; j++){
if(cur == newHead)
return cur;
newHead = newHead.next;
}
newHead = head;
cur = cur.next;
i += 1;
}
return null;
}
所以我们要接着向改进的方法,这里考虑上道题目中使用的fast和slow两个指针的思路,当存在环时,fast最终会追上slow,可是二者交汇的位置并不一定是环的起始位置,如何找到还的起始点呢??有一个很巧妙的想法,那就是当fast==slow时,在新创一个节点从head开始遍历,当他和slow相遇时,就是换的开始位置。大家可以画一个图结合起来思考,这里我们假设链表长度为l,当fast与slow相遇时slow走了x,fast走了2x,那么2x=l+y,y表示环的起始位置到slow的长度,那么从头到环开始位置的长度假设为z,所以就有下面的等式:
2x = l+y
x = z+y
所以l-x = z, 也就是说这样新的节点与slow会在环开始的地方相遇
代码如下所示:
public ListNode detectCycle2(ListNode head){
ListNode slow=head, fast=head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
ListNode tmp = head;
while(tmp != slow){
tmp = tmp.next;
slow = slow.next;
}
return slow;
}
}
return null;
}