题目连接:环形链表II
题目描述:返回链表开始入环的第一个节点,如果无环,则返回null。
方法一、哈希表
HashSet存储链表节点,遍历链表,当一个节点在哈希表中已存在时,说明链表有环,且该节点就是入环的第一个节点。
该方法用于判断链表是否有环,和链表入环第一个节点,思路一模一样。
时间复杂度O(n),空间复杂度O(n)。
代码如下:
public static ListNode detectCycle(ListNode head) {
Set<ListNode> set=new HashSet<>();
while (head!=null){
if(set.contains(head))
return head;
set.add(head);
head=head.next;
}
return null;
}
方法二:快慢指针 两次相遇
双指针第一次相遇:
慢指针slow每次移动一个节点,快指针fast每次移动两个节点,如果链表无环,那么一定是fast先到达终点,即为null。
如果链表有环,那么slow和fast一定会在环内相遇,即fast == slow。而且相遇时,fast一定比slow多走了n个环的长度。(入环之后,两个指针就一直在环内绕圈。)
设从起点到入环点的节点长度为a,环内节点的长度为b,相遇时slow走过的长度为s,fast走过的长度为f,因为fast每步走两个节点,slow每步走一个节点,那么一定有f=2s。又因为相遇时fast比slow多走了n个环的长度,所以有f-s=nb。
解得s=nb,f=2nb。即相遇时,fast和slow分别走了2n、n个环的周长,总步长为2nb,nb。
双指针第二次相遇:
已知若一个指针从链表起点出发,每次走到入环点时,走过的长度为k=a+nb。慢指针slow从起点出发到相遇点,已走过nb步,那么再走步长a就能到达入环点。
a为起点到入环点的长度,是未知的。但是从起点走步长a也能到达入环点。
此时让一个指针third从起点出发,slow从fast和slow的相遇点出发,以相同的速度,当两者相遇,即third ==slow时,就都走过步长a,刚好到达入环点。(一个从环外出发,一个在环内转圈,两者能够相遇一定是在环内。third从起点出发到入环点走过a步,slow再走a步同样到达入环点。所以相遇就是在入环点)
时间复杂度O(n),空间复杂度O(1)。
代码如下:
public static ListNode detectCycle(ListNode head){
if(head==null)
return null;
ListNode slow=head,fast=head;
while (fast!=null){
slow=slow.next;
if(fast.next==null)
return null;
fast=fast.next.next;
// 相遇,说明成环,
if(fast==slow){
//从起点出发
fast=head;
while (fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
return null;
}