环形链表
给定一个链表,判断链表中是否有环。
进阶:
你能否不使用额外空间解决此题?
通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A) - 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。
其他情况又会怎样呢?例如,我们没有考虑快跑者在慢跑者之后两步或三步的情况。但其实不难想到,因为在下一次或者下下次迭代后,又会变成上面提到的情况 A。
1 public boolean hasCycle(ListNode head){ 2 if(head==null||head.next==null){ 3 return false; 4 } 5 ListNode slow=head; 6 ListNode fast=head.next; 7 while(slow!=fast){ 8 if(fast==null||fast.next==null){ 9 return false; 10 } 11 slow=slow.next; 12 fast=fast.next.next; 13 } 14 return true; 15 }
复杂度分析
-
时间复杂度:O(n)O(n)O(n), 让我们将 nnn 设为链表中结点的总数。为了分析时间复杂度,我们分别考虑下面两种情况。
- 链表中不存在环:
快指针将会首先到达尾部,其时间取决于列表的长度,也就是 O(n)O(n)O(n)。 -
链表中存在环:
我们将慢指针的移动过程划分为两个阶段:非环部分与环形部分:- 慢指针在走完非环部分阶段后将进入环形部分:此时,快指针已经进入环中 迭代次数=非环部分长度=N ext{迭代次数} = ext{非环部分长度} = N迭代次数=非环部分长度=N
- 两个指针都在环形区域中:考虑两个在环形赛道上的运动员 - 快跑者每次移动两步而慢跑者每次只移动一步。其速度的差值为1,因此需要经过 二者之间距离速度差值dfrac{ ext{二者之间距离}}{ ext{速度差值}}速度差值二者之间距离 次循环后,快跑者可以追上慢跑者。这个距离几乎就是 "环形部分长度 K ext{环形部分长度 K}环形部分长度 K" 且速度差值为 1,我们得出这样的结论 迭代次数=近似于 ext{迭代次数} = ext{近似于}迭代次数=近似于 "环形部分长度 K ext{环形部分长度 K}环形部分长度 K".
因此,在最糟糕的情形下,时间复杂度为 O(N+K)O(N+K)O(N+K),也就是 O(n)O(n)O(n)。
- 链表中不存在环:
- 空间复杂度:O(1)O(1)O(1), 我们只使用了慢指针和快指针两个结点,所以空间复杂度为 O(1)O(1)O(1)。