141. Linked List Cycle

Easy 9,512 854 Accepted: 1,642,833 Submitted: 3,531,009 Acceptance Rate: 46.53%

iven head, the head of a linked list, determine if the linked list has a cycle in it.

There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter.

Return true if there is a cycle in the linked list. Otherwise, return false.

Analysis

1. What should we return if the input is null?
2. What can the range of linked list and cycle be?
3. What is the range of node values? Can there be duplicate values?
4. Is a cycle determined by values or the nodes?
5. Can the cycle start at any node?
6. Can a node point to itself?

Design

1. One thought would be to have two pointers where one pointer acts as a reference to see if the node was seen before. The other pointer would move ahead one at a time until it reaches the end or sees a repeated node. However, we would need something to keep track of previously seen nodes since the initial reference might not be in the cycle. If we have that something, the initial pointer becomes redundant.
2. Since we need to keep track of seen nodes, we can use a set (assuming the nodes are hashable). We can iterate through the list and insert the nodes into the set. We check if the current node is in the set to determine if there is a cycle. This would give us a linear time and space solution.
3. Can we do better? We can use the two pointers approach and move them at different speeds, one pointer jumps one node at a time and the other can jump 2x that. If there is a cycle, the pointers will intersect at one point. Otherwise, the pointers will reach the end. This is Floyd's cycle finding algorithm. This gives us linear speed and constant space.
4. There is a faster algorithm known as Brent's but it isn't asymptotically faster. We can stick with Floyd's.

Code

class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if head is None:
return False

slow, fast = head, head.next

while fast is not None and fast.next is not None and slow != fast:
slow = slow.next
fast = fast.next.next

return slow == fast

Test

1. Empty list
2. No cycles
3. Cycle starts at first element
4. Cycle starts at non-first element

Complexity Analysis

O(n) for speed since the slow pointer will go through each node a few times in a cycle scenario before it lands on the same node as the fast pointer.

O(1) space

Post-Solving Commentary

1. Implementing this is surprisingly trickier than expected, mostly in terms of all the null checks needed. It is rather easy to add unnecessary checks.
2. Looking at the LeetCode solutions, there are many ways to simplify the code. One involves using catching exceptions. Here is another approach where slow and fast start off at head.