Start learning free

LESSON 309 OF 437 · LINKED LISTS

Linked List Cycle in C++: Floyd's Slow and Fast Pointers

Practice O(n) Read the full explanation

Given 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. In the examples, pos is the index of the node that the tail's next pointer is connected to (-1 means no cycle). pos only describes how the example list is built: the tests build the cycle themselves, and pos is not passed to your function, which receives only head.

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

 

Example 1:

Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed):
3 -> 2 -> 0 -> -4, and -4's next is the node 2 again.

Example 2:

Input: head = [1,2], pos = 0
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 0th node:
1 -> 2, and 2's next is the node 1 again.

Example 3:

Input: head = [1], pos = -1
Output: false
Explanation: There is no cycle in the linked list.

 

Constraints:

  • The number of the nodes in the list is in the range [0, 104].
  • -105 <= Node.val <= 105
  • pos is -1 or a valid index in the linked-list.

 

Follow up: Can you solve it using O(1) (i.e. constant) memory?

STEP 1 OF 6
    Ctrl + Enter to check

    C++ · LINKED LISTS

    Linked List Cycle in C++, explained

    Problem overview

    The method hasCycle receives head, a pointer to the first ListNode of a singly linked list. It returns true if following the next pointers from head can lead back to a node that was already visited, which means the list contains a cycle. It returns false if the list ends, that is, if some node has next == NULL.

    The statement mentions a number pos, the index of the node that the tail connects back to. It only describes how the tests build their lists. The method never receives it, so it has to detect the cycle from the nodes alone. The list can also be empty (head is null), because the number of nodes is allowed to be 0.

    In the first example the list is 3 → 2 → 0 → -4, and the last node points back to the node holding 2, so the answer is true. In the third example there is a single node whose next is null, so the answer is false.

    Approach: two pointers at different speeds

    The direct idea is to remember every node you visit. Walk along the list, store each node's address in an unordered_set<ListNode*>, and report a cycle if you ever meet an address that is already in the set:

    bool hasCycleWithSet(ListNode* head) {
        unordered_set<ListNode*> seen;
        for (ListNode* node = head; node != nullptr; node = node->next) {
            if (!seen.insert(node).second) {
                return true;
            }
        }
        return false;
    }
    

    This is correct and runs in O(n) time, but it uses O(n) extra memory. The statement's follow-up asks for constant memory, and the lesson solves it with Floyd's tortoise and hare algorithm.

    The algorithm uses two pointers that both start at head. The pointer slow moves one node per step and fast moves two nodes per step.

    • If the list has no cycle, fast reaches the end first. The loop stops and the answer is false.
    • If the list has a cycle, both pointers eventually run around it. fast keeps catching up with slow, and at some point they point to the same node. The answer is true.

    Why the pointers must meet

    Once both pointers are inside the cycle, look at the distance from fast forward around the loop to slow. In every step fast advances two nodes and slow advances one, so that distance shrinks by exactly one. A distance that goes down one at a time cannot jump over zero, so it reaches zero, and zero means both pointers are on the same node. Because the gap changes by one, fast cannot step over slow without landing on it. With a larger speed difference, such as three nodes against one, the gap would shrink by two at a time and could jump over zero.

    A worked example

    Take the list 3 → 2 → 0 → -4 with the tail pointing back to the node holding 2. Both pointers start at the node holding 3.

    Stepslow is atfast is atSame node?
    120no
    202no
    3-4-4yes, return true

    Step by step

    1. Declare slow and fast as ListNode* and set both to head.
    2. Loop while fast != NULL && fast->next != NULL. fast jumps two nodes, so it needs two nodes ahead of it. If fast or fast->next is null, the list ends and there is no cycle.
    3. Move slow one node with slow = slow->next.
    4. Move fast two nodes with fast = fast->next->next. This is safe, because the loop condition just proved that fast->next is not null.
    5. Compare after moving. If slow == fast, return true. When the loop finishes without a meeting, return false.

    The solution in C++

    class Solution {
    public:
        bool hasCycle(ListNode* head) {
            ListNode* slow = head;
            ListNode* fast = head;
            while (fast != NULL && fast->next != NULL) {
                slow = slow->next;
                fast = fast->next->next;
                if (slow == fast) return true;
            }
            return false;
        }
    };
    

    The C++ details behind these lines:

    • The order of the two conditions matters. The operator && stops at the first false operand. If fast is null, fast->next is never evaluated. Swapping the two conditions would dereference a null pointer on lists with an even number of nodes and no cycle, which is undefined behaviour.
    • slow == fast compares addresses. It asks whether both pointers point to the same node, not whether the nodes hold equal values. A list such as 1 → 1 → 1 has equal values and no cycle.
    • NULL and nullptr. The starter code's constructors use nullptr, and this solution tests against NULL. For pointers the two comparisons behave the same here. In new code, nullptr is the preferred spelling.
    • The method does not modify the list and allocates nothing.

    Complexity

    Time: O(n). Without a cycle, fast reaches the end after about n / 2 iterations. With a cycle, slow needs at most n steps to get into the cycle, and after that the distance between the pointers closes by one per iteration, which is at most the cycle length. Together this is at most about n iterations.

    Space: O(1). The method keeps two pointers, however long the list is. This is the improvement over the set-based version.

    Edge cases and common mistakes

    • Empty list. With head == NULL the loop condition is false at once and the method returns false.
    • One node. If its next is null, the loop does not run and the answer is false. If its next points to itself, then one step later slow and fast are both that node, and the answer is true.
    • Comparing before moving. Both pointers start at head, so a check placed before the moves would always return true. The comparison belongs after the moves.
    • Checking only fast != NULL. The expression fast->next->next would then dereference null whenever fast stands on the last node.
    • Comparing values. Duplicated values do not mean a cycle. Compare the pointers.

    Testing

    • [3, 2, 0, -4] with the tail joined to index 1 gives true.
    • [1, 2] with the tail joined to index 0 gives true.
    • [1] with no cycle gives false.
    • [] (a null head) gives false.
    • [1, 2, 3, 4, 5] with no cycle gives false, and with the tail joined to index 4 (the last node points to itself) gives true.

    The same two pointers solve a related task: when fast reaches the end of a list without a cycle, slow stands on the middle node. For an even number of nodes, it is the second of the two middle ones.

    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free