Facebook Pixel

141. Linked List Cycle

Problem Description

You are given the head of a linked list. Your task is to determine whether the linked list contains a cycle.

A cycle exists in a linked list when a node can be reached again by continuously following the next pointers. In other words, if you keep traversing the list by moving from one node to the next, and you eventually arrive at a node you've already visited, then the list has a cycle.

The problem uses an internal variable pos to denote which node the tail connects back to (creating the cycle), but this variable is not provided as input to your function. You only receive the head of the linked list.

Your function should return true if a cycle exists anywhere in the linked list, and false if the linked list has no cycle (meaning it eventually terminates with a node pointing to null).

The solution uses a hash table approach where each node is stored in a set as we traverse the list. If we encounter a node that already exists in our set, we've found a cycle and return true. If we reach the end of the list (a null pointer) without finding any repeated nodes, we return false.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When traversing a linked list, if there's no cycle, we'll eventually reach the end (a null pointer). However, if there's a cycle, we'll keep going around in circles forever, visiting the same nodes repeatedly.

The key insight is that in a cycle, we must revisit at least one node. Think of it like walking through rooms in a building - if there's no cycle, you'll eventually reach an exit. But if there's a cycle, you'll find yourself back in a room you've already been in.

To detect when we've entered a room we've seen before, we need to keep track of all the rooms (nodes) we've visited. A hash table (or set) is perfect for this because it allows us to quickly check if we've seen a node before in O(1) time.

As we traverse the linked list:

  • We check if the current node is already in our set
  • If it is, we've found our cycle - we're revisiting a node
  • If it's not, we add it to our set and continue
  • If we reach null, there's no cycle

This approach works because each node object in memory has a unique identity. When we store nodes in the set, we're storing references to these specific objects, not their values. So even if two nodes have the same value, they're different objects and won't create a false positive for cycle detection.

Solution Approach

The solution implements the hash table approach to detect cycles in a linked list. Here's how the implementation works:

Data Structure Used: A set s to store visited nodes.

Algorithm Steps:

  1. Initialize an empty set s to keep track of all nodes we've visited.

  2. Traverse the linked list using a while loop that continues as long as head is not None:

    while head:
  3. Check for cycle at each node:

    • Before processing the current node, check if it already exists in our set:
      if head in s:
          return True
    • If the node is in the set, we've detected a cycle and immediately return True.
  4. Mark the node as visited by adding it to the set:

    s.add(head)
  5. Move to the next node in the linked list:

    head = head.next
  6. Return False if no cycle found: If the loop completes (we reached a None pointer), the linked list has no cycle:

    return False

Time Complexity: O(n) where n is the number of nodes in the linked list. In the worst case, we visit all nodes once.

Space Complexity: O(n) for storing up to n nodes in the hash set.

The beauty of this approach is its simplicity - we're essentially keeping a "guest book" of all nodes we've visited. If we ever see a node trying to "sign in" twice, we know we've found a cycle.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate how the hash table approach detects a cycle.

Example: Linked list with a cycle

Consider this linked list: 1 -> 2 -> 3 -> 4 -> 2 (cycles back)

Where node 4's next pointer points back to the node with value 2, creating a cycle.

Step-by-step execution:

  1. Start at head (node 1)

    • Set s = {}
    • Is node 1 in s? No
    • Add node 1 to set: s = {node1}
    • Move to head.next (node 2)
  2. At node 2

    • Is node 2 in s? No
    • Add node 2 to set: s = {node1, node2}
    • Move to head.next (node 3)
  3. At node 3

    • Is node 3 in s? No
    • Add node 3 to set: s = {node1, node2, node3}
    • Move to head.next (node 4)
  4. At node 4

    • Is node 4 in s? No
    • Add node 4 to set: s = {node1, node2, node3, node4}
    • Move to head.next (back to node 2)
  5. Back at node 2

    • Is node 2 in s? Yes! (we added it in step 2)
    • Return True - cycle detected!

Counter-example: Linked list without a cycle

Consider: 1 -> 2 -> 3 -> None

Following the same process:

  • Visit node 1, add to set
  • Visit node 2, add to set
  • Visit node 3, add to set
  • Move to head.next which is None
  • Exit while loop
  • Return False - no cycle found

The key insight is that we're storing actual node objects (memory references), not values. So even if two nodes have the same value, they're different objects in memory and won't trigger a false positive.

Solution Implementation

1# Definition for singly-linked list.
2# class ListNode:
3#     def __init__(self, x):
4#         self.val = x
5#         self.next = None
6
7from typing import Optional
8
9
10class Solution:
11    def hasCycle(self, head: Optional[ListNode]) -> bool:
12        """
13        Detects if a linked list contains a cycle.
14      
15        Uses a hash set to track visited nodes. If we encounter a node
16        that's already in the set, we've found a cycle.
17      
18        Args:
19            head: The head node of the linked list
20          
21        Returns:
22            True if the linked list contains a cycle, False otherwise
23        """
24        # Set to store visited nodes
25        visited_nodes = set()
26      
27        # Traverse the linked list
28        current_node = head
29        while current_node:
30            # Check if we've seen this node before (cycle detected)
31            if current_node in visited_nodes:
32                return True
33          
34            # Add current node to visited set
35            visited_nodes.add(current_node)
36          
37            # Move to next node
38            current_node = current_node.next
39      
40        # No cycle found after traversing entire list
41        return False
42
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     int val;
5 *     ListNode next;
6 *     ListNode(int x) {
7 *         val = x;
8 *         next = null;
9 *     }
10 * }
11 */
12public class Solution {
13    /**
14     * Detects if a linked list contains a cycle.
15     * Uses a HashSet to track visited nodes.
16     * 
17     * @param head The head of the linked list
18     * @return true if the linked list contains a cycle, false otherwise
19     */
20    public boolean hasCycle(ListNode head) {
21        // HashSet to store visited nodes
22        Set<ListNode> visitedNodes = new HashSet<>();
23      
24        // Traverse the linked list
25        ListNode currentNode = head;
26        while (currentNode != null) {
27            // If add() returns false, the node was already in the set (cycle detected)
28            if (!visitedNodes.add(currentNode)) {
29                return true;
30            }
31            // Move to the next node
32            currentNode = currentNode.next;
33        }
34      
35        // No cycle found after traversing the entire list
36        return false;
37    }
38}
39
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 *     int val;
5 *     ListNode *next;
6 *     ListNode(int x) : val(x), next(NULL) {}
7 * };
8 */
9class Solution {
10public:
11    /**
12     * Detects if a linked list contains a cycle.
13     * Uses a hash set to track visited nodes.
14     * 
15     * @param head: pointer to the head of the linked list
16     * @return: true if cycle exists, false otherwise
17     */
18    bool hasCycle(ListNode* head) {
19        // Hash set to store visited node addresses
20        unordered_set<ListNode*> visitedNodes;
21      
22        // Traverse the linked list
23        ListNode* current = head;
24        while (current != nullptr) {
25            // Check if current node has been visited before (cycle detected)
26            if (visitedNodes.count(current) > 0) {
27                return true;
28            }
29          
30            // Mark current node as visited
31            visitedNodes.insert(current);
32          
33            // Move to next node
34            current = current->next;
35        }
36      
37        // No cycle found after traversing entire list
38        return false;
39    }
40};
41
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     val: number
5 *     next: ListNode | null
6 *     constructor(val?: number, next?: ListNode | null) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.next = (next===undefined ? null : next)
9 *     }
10 * }
11 */
12
13/**
14 * Detects if a linked list contains a cycle.
15 * Uses a hash set to track visited nodes.
16 * 
17 * @param head - The head node of the linked list
18 * @returns true if the linked list contains a cycle, false otherwise
19 * 
20 * Time Complexity: O(n) where n is the number of nodes
21 * Space Complexity: O(n) for storing visited nodes in the set
22 */
23function hasCycle(head: ListNode | null): boolean {
24    // Set to store visited nodes
25    const visitedNodes: Set<ListNode> = new Set<ListNode>();
26  
27    // Traverse the linked list
28    let currentNode: ListNode | null = head;
29  
30    while (currentNode !== null) {
31        // Check if we've seen this node before (indicates a cycle)
32        if (visitedNodes.has(currentNode)) {
33            return true;
34        }
35      
36        // Mark current node as visited
37        visitedNodes.add(currentNode);
38      
39        // Move to the next node
40        currentNode = currentNode.next;
41    }
42  
43    // No cycle found after traversing all nodes
44    return false;
45}
46

Time and Space Complexity

Time Complexity: O(n)

The algorithm traverses the linked list once using a while loop. In the worst case (when there is no cycle), it visits all n nodes exactly once. When there is a cycle, it visits at most n nodes before detecting a repeated node. Each operation inside the loop (checking membership in the set, adding to the set, and moving to the next node) takes O(1) average time for hash set operations. Therefore, the overall time complexity is O(n).

Space Complexity: O(n)

The algorithm uses a hash set s to store visited nodes. In the worst case (when there is no cycle), the set will contain all n nodes of the linked list. Even when there is a cycle, the set could potentially store up to n nodes before detecting the cycle. Therefore, the space complexity is O(n).

Common Pitfalls

1. Attempting to Use Node Values Instead of Node Objects

A common mistake is trying to detect cycles by storing node values in the set rather than the node objects themselves:

# INCORRECT approach
def hasCycle(self, head: Optional[ListNode]) -> bool:
    visited_values = set()
    current = head
    while current:
        if current.val in visited_values:  # Wrong! Checking values
            return True
        visited_values.add(current.val)
        current = current.next
    return False

Why this fails: Multiple nodes can have the same value without forming a cycle. For example, a list like 1 -> 2 -> 3 -> 2 -> 4 -> NULL has duplicate values but no cycle.

Solution: Always store the actual node objects (memory references) in the set, not their values:

if current in visited_nodes:  # Correct - checking node object
    return True
visited_nodes.add(current)     # Correct - storing node object

2. Memory Concerns with Large Lists

While the hash table approach is straightforward, it uses O(n) extra space. For very large linked lists, this could cause memory issues.

Alternative Solution: Use Floyd's Cycle Detection Algorithm (Tortoise and Hare) for O(1) space complexity:

def hasCycle(self, head: Optional[ListNode]) -> bool:
    if not head or not head.next:
        return False
  
    slow = head
    fast = head.next
  
    while slow != fast:
        if not fast or not fast.next:
            return False
        slow = slow.next
        fast = fast.next.next
  
    return True

3. Modifying the Original List Structure

Some might be tempted to mark visited nodes by modifying them (e.g., setting a special value or adding a visited flag), which alters the original data structure:

# INCORRECT - modifies the original list
def hasCycle(self, head: Optional[ListNode]) -> bool:
    current = head
    while current:
        if hasattr(current, 'visited'):  # Bad practice
            return True
        current.visited = True  # Modifying original node
        current = current.next
    return False

Why this is problematic:

  • Violates the principle of not modifying input data
  • May cause issues if the list is used elsewhere
  • Not thread-safe

Solution: Stick with the hash set approach or Floyd's algorithm, both of which leave the original list intact.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More