今天这道题是 LeetCode链表篇:141.环形链表 的后续篇,区别点在于,《LeetCode链表篇:141.环形链表》只要找到链表是否有环即可,而《LeetCode链表篇:142.环形链表 II》需要找到链表开始入环的节点。
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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 解释:链表中没有环。
进阶:你是否可以不用额外空间解决此题?
其实思路和 LeetCode链表篇:141.环形链表 是一样的,通过循环遍历所有的节点,同时通过哈希表来存储已经遍历的节点,每遍历一个节点,就判断当前节点是否存在于哈希表中,如果存在,则说明是环形链表,只是这里不再返回 True,而是返回入环的节点,反之就把当前节点存储到哈希表中。
时间复杂度:O(n),对于含有 n 个元素的链表,我们访问每个元素最多一次,添加一个节点到哈希表中只需要花费 O(1) 的时间,最坏情况下我们需要遍历每个节点一次。
空间复杂度:O(n),空间取决于添加到哈希表中的节点数目,最坏的情况需要将每个元素都插入哈希表中。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
hash={}
while head:
if head not in hash:
hash[head]=0
head=head.next
else:
return head
return None
思路:
但是不同点来了,这里我们需要找到入环的节点,所以我们要计算入环节点的位置,让我们一起来看下图:
根据我们的定义,慢指针每次移动一步,快指针每次移动两步,我们可以得到:x+n(y+z)+y=2(x+y) => x=(n-1)(y+z)+z。
从推导的关系我们可以看出:从相遇点到入环点的距离再加上(n-1)圈的环长度,恰好等于链表头节点到入环点的距离。
因此,当慢指针和快指针相遇时,我们把慢指针重新指向链表头节点,之后,再让慢指针和快指针都只向后移动一个位置,这样它们就会在入环点相遇,我们就找到了入环点。
时间复杂度:O(n),在最初判断快慢指针是否相遇时,慢指针走过的距离不会超过链表的总长度,随后寻找入环点时,走过的距离也不会超过链表的总长度,所以总时间为 O(n)+O(n)=O(n)。
空间复杂度:O(1),我们只使用了 slow,fast 两个指针。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head or not head.next:
return None
slow,fast=head,head
while fast and fast.next:
slow=slow.next
fast=fast.next.next
if slow == fast:
slow=head
while slow != fast:
slow=slow.next
fast=fast.next
return slow
return None
最近写的题都不是很难,基本上都是 easy 级别的题,今天这个题是 medium 级别的,但是也不是很难。还有我画图不是很好看,凑合看啦,哈哈。因为近期时间不是很多,后面想好好找点时间写下图,同时把岛屿系列的题加在里面形成一个比较完整的图知识点,里面会涉及到拓扑排序、深度优先遍历、广度优先遍历等内容,但是花的时间会比较长,敬请大家期待噢。
如果觉得文章不错,希望大家可以点击上方名片关注我噢,点赞、收藏、在看、分享就再好不过了。如果有任何建议和问题,可以在下方给我留言,我会及时回复的,同时会不定期更新更多的文章,祝我们终将自由。