Part I · Arrays, Strings and Pointers Pattern 3 4 problems

Fast and Slow Pointers

Two walkers on the same track at different speeds. If the track loops, the fast one laps the slow one, and that collision is a cycle detector using no memory at all.

Also called Floyd’s tortoise and hare. It answers a question that looks like it needs a hash set, have I been here before, using two pointers and nothing else. The interviewer is usually testing whether you can get from the O(n)-space answer to the O(1)-space answer.

Contents

  1. When to use
  2. Core idea
  3. Why they must meet, and where
  4. The templates
  5. Common mistakes
  6. Linked List Cycle
  7. Middle of the Linked List
  8. Happy Number
  9. Find the Duplicate Number
  10. Recap

When to use

The trigger. There is a successor function: from any state there is exactly one next state. A linked list node has one next. An index i maps to nums[i]. A number maps to the sum of the squares of its digits. Any such function traces a path that must eventually repeat, and the question is about that repetition or about a position along the path.

The mental move that unlocks the array problems

An array can be a linked list. If every value is a valid index, then i → nums[i] is a next pointer and the array is a linked structure. Once you see that, “find the duplicate” becomes “find the start of the cycle”. Recognising this disguise is the single highest-value thing on this page.

Core idea

Move slow one step and fast two steps per iteration. If the path is finite and has no cycle, fast falls off the end and you are done. If there is a cycle, both pointers end up inside it, and since fast gains exactly one position on slow every iteration, the gap shrinks by one each time and must hit zero. They cannot jump past each other.
h S tail, length μ = 3 cycle start M M = meeting point cycle length λ = 5 slow walks μ + k steps fast walks 2(μ + k) steps so μ + k is a multiple of λ
Figure 3.1 — The rho shape. Every successor-function path looks like this: a tail of length μ feeding a cycle of length λ. Either part may be empty.

Reading the figure. The straight run on the left is the tail, μ nodes long. The loop is the cycle, λ nodes long. M is where the two pointers meet. slow has walked μ + k steps and fast twice that, so μ + k is a whole number of laps.

Why they must meet, and where

Part 1: they meet

Once both pointers are inside the cycle, let d be the number of positions fast is behind slow, measured going forward around the cycle. Each iteration fast advances 2 and slow advances 1, so d decreases by exactly 1, modulo the cycle length. It therefore reaches 0 within λ iterations. Because the change is exactly one per step, they cannot step over each other, which is why the speeds must be 1 and 2 and not, say, 1 and 3.

Part 2: the meeting point locates the cycle start

Let μ be the tail length and λ the cycle length. When they meet, slow has taken μ + k steps for some k, and fast has taken 2(μ + k). Their difference, μ + k, must be a whole number of laps, so μ + k ≡ 0 (mod λ).

Now restart one pointer at the head and leave the other at the meeting point, and advance both one step at a time. After μ steps the first is at the cycle start. The second has taken μ + k + μ steps in total, and since μ + k is a multiple of λ, that lands it exactly μ steps into the cycle too. They meet at the cycle start.

This second phase is what turns cycle detection into cycle location, and it is the whole trick behind Find the Duplicate Number.

The templates

Node definition used on this page
/// <summary>A singly linked list node. A class, not a record, so == means identity.</summary>
/// <example><c>new ListNode(1, new ListNode(2))</c> is the list 1 -> 2.</example>
public class ListNode(int val = 0, ListNode? next = null)   // 0: a neutral default value
{
    public int Val = val;
    public ListNode? Next = next;      // null: no next node, the list ends here
}
Template A — detect a cycle
public static class CycleDetect
{
    /// <summary>Return the meeting node if a cycle exists, else null.</summary>
    /// <param name="head">First node, or null for an empty list.</param>
    /// <returns>A node inside the cycle where slow and fast meet, or null.</returns>
    /// <example>For 1 -> 2 -> 1 (a loop), <c>Detect(head)</c> returns a node in it.</example>
    public static ListNode? Detect(ListNode? head)
    {
        ListNode? slow = head, fast = head;   // both start at the head

        // Invariant: fast has taken twice as many steps as slow.
        // Checking fast and fast.Next first keeps the two-step move safe.
        while (fast is not null && fast.Next is not null)
        {
            slow = slow!.Next;           // 1 step. !: slow trails fast, so it is not null
            fast = fast.Next.Next;       // 2 steps
            // In a cycle, fast gains 1 node per pass, so it must land on slow.
            if (slow == fast)            // class ==: same object, not same value
                return slow;
        }

        return null;                     // fast ran off the end: no cycle
    }
}

The guard fast is not null && fast.Next is not null covers both odd and even list lengths. Test fast before you read fast.Next. The ! on slow!.Next tells the compiler what the guard already proves: slow trails fast, so it is never null there.

Template B — locate the cycle start
public static class CycleLocate
{
    /// <summary>Return the first node of the cycle, or null if there is none.</summary>
    /// <param name="head">First node, or null.</param>
    /// <returns>The node where the tail joins the cycle, or null.</returns>
    /// <example>For 3 -> 2 -> 0 -> -4 -> back to 2, <c>CycleStart(head).Val</c> is 2.</example>
    public static ListNode? CycleStart(ListNode? head)
    {
        ListNode? meeting = CycleDetect.Detect(head);
        if (meeting is null)
            return null;                 // no cycle, so no start node

        // The head and the meeting point sit the same distance from the cycle start.
        // So two pointers that move at the same speed meet exactly there.
        ListNode walker = head!;         // !: a cycle exists, so head is not null
        // Invariant: walker and meeting are the same distance from the cycle start.
        while (walker != meeting)        // both move one step at a time
        {
            walker = walker.Next!;       // !: inside a rho path every node has a Next
            meeting = meeting.Next!;
        }

        return walker;
    }
}
h S M finder slow cycle start finder: 3 steps to S μ = 3 and λ = 5, phase 1 met at M finder starts at the head h slow stays at M both now move one step per turn slow needs 3 steps from M to S too so they meet at S, the cycle start
Figure 3.2 — In phase 2 the head and the meeting point are the same distance from the cycle start.

Reading the figure. Amber nodes are where the two pointers start phase 2. Green S is the cycle start, where they meet. Count the arrows: three from h to S, and three from M forward to S.

Common mistakes

The problems

1. Linked List Cycle Easy

Problem

Given the head of a linked list, return true if the list has a cycle in it. Solve it with O(1) extra memory.

Approach

Solution

public static class LinkedListCycle
{
    /// <summary>Return true if the linked list contains a cycle.</summary>
    /// <param name="head">First node, or null for an empty list.</param>
    /// <returns>True if following Next repeats a node forever.</returns>
    /// <example>For 3 -> 2 -> 0 -> -4 -> back to 2, <c>HasCycle(head)</c> returns true.</example>
    public static bool HasCycle(ListNode? head)
    {
        ListNode? slow = head, fast = head;   // both start at the head

        // Invariant: fast has taken twice as many steps as slow.
        // Checking fast and fast.Next first keeps the two-step move safe.
        while (fast is not null && fast.Next is not null)
        {
            slow = slow!.Next;           // 1 step. !: slow trails fast, so it is not null
            fast = fast.Next.Next;       // 2 steps: gains 1 node on slow per pass
            if (slow == fast)            // identity: same object
                return true;
        }

        return false;                    // fast hit the end, so the list has no loop
    }
}

Walkthrough

List 3 → 2 → 0 → -4 with -4 pointing back to 2:

start 3 2 0 −4 slow, fast slow 3, fast 3 iteration 1 3 2 0 −4 slow fast slow 2, fast 0 iteration 2 3 2 0 −4 slow fast slow 0, fast 2 iteration 3 3 2 0 −4 slow, fast they meet: true
Figure 3.3 — Fast gains one node per turn inside the loop, so it catches slow at −4 after three turns.

Reading the figure. Each panel is one turn. Amber nodes hold slow or fast. The curved arrow is the link from −4 back to 2 that makes the cycle. Green is the meeting node.

They meet at -4, so the answer is true.

TimeO(n)SpaceO(1)

Time bound: slow takes at most μ steps to enter the cycle, then at most λ more before fast catches it, so at most μ + λ ≤ n iterations.

Edge cases to raise

Say this out loud: “The gap between them shrinks by exactly one per step, so the fast pointer cannot skip over the slow one. That is why the speeds have to be 1 and 2.”

2. Middle of the Linked List Easy

Problem

Return the middle node of a singly linked list. If there are two middles, return the second one.

Approach

Solution

public static class MiddleOfList
{
    /// <summary>Return the middle node. The second middle if the length is even.</summary>
    /// <param name="head">First node, or null.</param>
    /// <returns>The middle node, or null for an empty list.</returns>
    /// <example>For 1 -> 2 -> 3 -> 4 -> 5, <c>MiddleNode(head).Val</c> is 3.</example>
    public static ListNode? MiddleNode(ListNode? head)
    {
        ListNode? slow = head, fast = head;   // both start at the head

        // Invariant: fast has taken twice as many steps as slow.
        // So when fast reaches the end, slow is halfway.
        while (fast is not null && fast.Next is not null)
        {
            slow = slow!.Next;           // 1 step. !: slow trails fast, so it is not null
            fast = fast.Next.Next;       // 2 steps
            // Even length: fast ends at null, which leaves slow on the second middle.
        }

        return slow;
    }
}

The two variants, and how to remember them

The second variant needs a non-empty list, so guard head first. For a list of length 4 the first gives node 3 and the second gives node 2.

Walkthrough

List 1 → 2 → 3 → 4 → 5. After two iterations slow is at 3 and fast is at 5, whose Next is null, so the loop stops. Answer 3. For 1 → 2 → 3 → 4, slow ends at 3, the second middle.

start 1 2 3 4 5 slow, fast both start at the head iteration 1 1 2 3 4 5 slow fast slow +1, fast +2 iteration 2 1 2 3 4 5 slow fast fast.Next is null, so stop answer: slow at 3 even, length 4 1 2 3 4 slow fast = null after 2 turns slow at 3, the second middle
Figure 3.4 — When fast reaches the end, slow has gone half as far and sits on the middle.

Reading the figure. Amber cells hold slow or fast. Green is the node returned. In the last row fast has run off the end, so it has no cell.

TimeO(n)SpaceO(1)

Where this shows up as a subroutine

Say this out loud: “One pass, and it works even if I can only read the list once, which the count-then-walk version cannot.”

3. Happy Number Easy

Problem

Start with a positive integer. Replace it by the sum of the squares of its digits, and repeat. The number is happy if this eventually reaches 1. Otherwise it loops forever without reaching 1. Return whether n is happy.

Approach

Solution

public static class HappyNumber
{
    /// <summary>Return true if repeatedly summing squared digits reaches 1.</summary>
    /// <param name="n">A positive integer.</param>
    /// <returns>True if n is a happy number.</returns>
    /// <example><c>IsHappy(19)</c> returns true.</example>
    public static bool IsHappy(int n)
    {
        // The sequence n, next(n), ... is a linked list of numbers.
        // It either hits 1 and stays there, or loops. Floyd tells the two apart.
        int slow = n, fast = NextValue(n);   // fast starts 1 step ahead so the loop can run

        // Stop on success (fast reaches the 1 fixed point) or on a collision.
        // 1: NextValue(1) == 1, so 1 is a loop of length one.
        while (fast != 1 && slow != fast)
        {
            slow = NextValue(slow);              // 1 step
            fast = NextValue(NextValue(fast));   // 2 steps
        }

        return fast == 1;                // 1: we stopped because we reached 1
    }

    /// <summary>Sum of the squares of the decimal digits of value.</summary>
    /// <param name="value">A non-negative integer.</param>
    /// <returns>The digit-square sum.</returns>
    /// <example><c>NextValue(19)</c> returns 82.</example>
    public static int NextValue(int value)
    {
        int total = 0;                   // 0: empty sum
        // Invariant: total holds the squares of the digits already peeled off.
        while (value > 0)                // 0: no digits left
        {
            int digit = value % 10;      // % 10: base ten, so this is the last digit
            value /= 10;                 // / 10: drop that digit
            total += digit * digit;      // digit squared
        }
        return total;
    }
}

Walkthrough

n = 19. The sequence is 19, 82, 68, 100, 1.

start 19 82 68 100 1 slow fast slow 19, fast 82 step 1 19 82 68 100 1 slow fast slow 82, fast 100 step 2 19 82 68 100 1 slow fast fast reaches 1: happy n = 2 2 4 16 37 58 89 145 42 20 2 leads into a cycle of 8 values slow and fast meet inside it 1 is never reached, so not happy
Figure 3.5 — Floyd works on the digit-square sequence: 19 reaches 1, while 2 falls into a cycle without 1.

Reading the figure. Each arrow is one call to NextValue. Amber cells hold slow or fast. Green 1 means happy. The red ring is the cycle that 2 falls into, where the pointers would meet without ever seeing 1.

For n = 2 the sequence enters the cycle 4, 16, 37, 58, 89, 145, 42, 20, 4, the pointers collide inside it, and the answer is false.

TimeO(log n)SpaceO(1)

The first NextValue call is O(log n) in the number of digits and collapses n to at most 243 immediately, after which the work is bounded by a constant.

Edge cases to raise

Say this out loud: “There is no list, but there is a deterministic successor over a finite state space, so the sequence has to end in a cycle. That is the only precondition Floyd needs.”

4. Find the Duplicate Number Medium

Problem

An array nums of n + 1 integers holds values in the range 1..n. Exactly one value is repeated, possibly many times. Find it, without modifying the array and using O(1) extra space.

Why the constraints matter

Binary search on the value range is the other accepted answer: for each candidate m, count how many values are ≤ m. If that count exceeds m, the duplicate is at or below m. That is O(n log n) time and O(1) space, and it is easier to derive under pressure. Have it as your backup.

Approach

Solution

public static class FindDuplicate
{
    /// <summary>Find the one repeated value in nums, read-only and in O(1) space.
    /// Treats nums as a functional graph i -> nums[i]. The repeated value is the
    /// entrance of the cycle that walk falls into.</summary>
    /// <param name="nums">n + 1 integers, each in 1..n, with exactly one value repeated.</param>
    /// <returns>The repeated value.</returns>
    /// <example><c>Find([1, 3, 4, 2, 2])</c> returns 2.</example>
    public static int Find(int[] nums)
    {
        // Phase 1: find a meeting point inside the cycle.
        // nums[0]: index 0 is the start. No value is 0, so nothing points back to it.
        int slow = nums[0], fast = nums[0];
        // Invariant: fast has taken twice as many steps as slow.
        // do-while: move first, because the pointers start equal.
        do
        {
            slow = nums[slow];           // 1 step
            fast = nums[nums[fast]];     // 2 steps
        } while (slow != fast);          // a cycle always exists, so this ends

        // Phase 2: walk one pointer from the start. They meet at the entrance.
        // Same trick as CycleStart: equal distances to the entrance.
        int finder = nums[0];            // nums[0]: one step from the start, like slow
        // Invariant: finder and slow are the same distance from the entrance.
        while (finder != slow)
        {
            finder = nums[finder];       // 1 step
            slow = nums[slow];           // 1 step
        }

        return finder;
    }
}

Walkthrough

nums = [1, 3, 4, 2, 2]. The walk from index 0 is 0 → 1 → 3 → 2 → 4 → 2 → 4 → …, a tail of 0, 1, 3 feeding the cycle 2, 4. Two arrows point at node 2, from index 3 and from index 4, and those are the two positions holding the value 2.

nums i=0 1 i=1 3 i=2 4 i=3 2 i=4 2 0 1 3 2 4 tail 0, 1, 3 cycle 2, 4 index i points to index nums[i] phase 1 (slow +1, fast +2): slow 1, 3, 2 fast 1, 2, 2 → meet at 2 phase 2 (both +1): finder 1, 3, 2 slow 2, 4, 2 → meet at 2
Figure 3.6 — The duplicate value 2 is the node with two arrows in, which is the cycle start.

Reading the figure. Green cells hold the repeated value, and green node 2 is the answer. Two arrows enter it: from index 3 and from index 4. Violet node 4 is the rest of the cycle. The text on the right lists where each pointer sits after each step.

They meet at 2, the answer.

TimeO(n)SpaceO(1)Writesnone

Edge cases to raise

The do-while shape matters. Phase 1 must move before comparing. The pointers start equal, so a top-tested while (slow != fast) would exit at once. C# has a real do { ... } while (slow != fast); loop, which says this exactly. Python needs while True with a break for the same thing.
Say this out loud: “Values are in 1 to n and indices in 0 to n, so nums is a next pointer and the array is a linked list. Two indices holding the same value are two arrows into one node, which is a cycle entrance, so the duplicate is exactly the cycle start.”

Recap

The six things to carry forward

Where this goes next

Pattern 4, Merge Intervals, leaves pointers behind. It is the first pattern where the whole insight is what to sort by, and where a greedy sweep replaces a search.


← 02 — Two Pointers 04 — Merge Intervals →