题意描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
进阶:
你是否可以不用额外空间解决此题?
示例:
- 示例 1
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
- 示例 2
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
- 示例 3
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
解题思路:
Alice: 这道题是 141-环形链表的增强版啊,不过还是不经打呀。
Bob: 对,这道题不仅仅问了是不是有环,还问了环开始的位置。不过你说不经打是你已经有解法了吗 ?
Alice: 你傻呀 ?还是第一种解法啊,打个标记呗,第一个重复访问的节点就是环的首节点啊。
Bob: 对啊,你好棒啊。那进阶怎么办,你是否可以不用额外空间解决此题?
Alice: 你说怎么办 ?😎
Bob: 还得从双指针的办法入手,双指针只能保证最终在环上的某个节点相遇并不能保证在首节点相遇,得想个办法再把首节点确定。
Alice: 有道理,现在能够知道的有一个链表,链表中环上的某个节点。问题是如何确定首个在环中的节点。
Bob: 啊啊啊啊,我有办法了,从 head
开始往后找,第一个出现在环上的节点就是答案。
Alice: 哦豁,那怎么判断一个节点是不是在环上呢 ?等等,我知道了,遍历整个环就好了,一个一个的检查。
Bob: 对呀,不过这样就写成了二重循环了,也就是 O(n^2)
的时间复杂度,不过内存倒是满足进阶的要求了。
Alice: 先码出来再说,说不定有更好的计算,遍历整个环的话明显是在重复计算了。
Bob: 好~
---------------------------------------------------------------------- Alice&Bob -----------------------------------------------------------------------------
Alice: 我明白了,需要定量证明一下。我先简述一下,先说结论,快慢两个指针以 1倍 和2倍 的速度 同时从 head
出发,在环上第一次相遇后,相遇的节点到环的入口的距离和 head
到环的入口的距离相等。
Bob:我去把别人的图拿来用一下吧,直接说不太好懂。
Alice: distance(slow) * 2 == distance(fast)
=> 2 * (F + a) = F + a + b + a
=> F = b
Bob: 哇,这个真是最好的解法了。我居然没有仔细考虑距离的问题,如果距离是成倍的就能用双指针确定环开始的位置。
Alice: 👱♀️
代码:
Python 方法一: 使用 set
标记访问过的节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
nodeAddress = set()
while head != None:
if head in nodeAddress:
return head
nodeAddress.add(head)
head = head.next
return None
Java 方法一: 使用 HashSet
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
HashSet nodeAddress = new HashSet();
while (head != null){
if(nodeAddress.contains(head)){
return head;
}
nodeAddress.add(head);
head = head.next;
}
return null;
}
}
Python 方法二: 双指针 + 一个判断
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
cycleNode = self.wing(head)
# cycleNode 为环中任一节点,也可能是首节点,也可能不是。
if cycleNode == None:
return None
else:
# 寻找环中的首节点
while head != cycleNode:
# 从head开始尝试,环中的节点肯定在环中,从head往后找第一个出现在环中的节点就是首节点。
node = cycleNode.next
# 从cycleNode.next -> cycleNode 遍历整个环
while node != cycleNode:
if node == head:
# 某个节点出现在环中
return head
# 立即返回,此时的节点及时首节点
node = node.next
head = head.next
return cycleNode
# 如果一直都找不到,说明cycleNode就是首节点,首节点之前不会再有任何节点在环中。
def wing(self, head: ListNode) -> ListNode:
# 检测是否有环,无环返回 None, 有环则返回环中任何一个节点
if head:
slow = head
fast = head.next
else:
return None
while slow != None and fast != None and fast.next != None:
if slow == fast:
return fast
slow = slow.next
fast = fast.next.next
return None
Java 方法二:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode cycleNode = wing(head);
if(cycleNode == null){
return null;
}
while (head != cycleNode){
ListNode node = cycleNode.next;
while(node != cycleNode){
if(node == head){
return head;
}
node = node.next;
}
head = head.next;
}
return cycleNode;
}
public ListNode wing(ListNode head){
if(head == null){
return null;
}else{
ListNode slow = head;
ListNode fast = head.next;
while(slow != null && fast != null && fast.next != null){
if(slow == fast){
return fast;
}
slow = slow.next;
fast = fast.next.next;
}
return null;
}
}
}
Python 方法三: 终极解法,将双指针贯彻到底。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
cycleNode = self.wing(head)
if cycleNode == None:
return None
else:
outter = head
inner = cycleNode
while outter != inner:
outter = outter.next
inner = inner.next
return outter
def wing(self, head: ListNode) -> ListNode:
if head == None:
return None
else:
slow = head
fast = head
while slow != None and fast != None and fast.next != None:
slow = slow.next
fast = fast.next.next
if slow == fast:
return fast
return None
易错点:
- 使用双指针法的时候,主要
fast.next fast.next.next
需要先保证fast != None and fast.next != None
- 快慢双指针相遇的位置确定在环上,但是不确定在环上的哪个位置。
总结:
为了 0.2M
内存,至于嘛 ?