Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window
P1EasyMental model~7 min speedrun

Fast & Slow Pointers (Floyd's Tortoise and Hare)

Grows into
Fixed-Offset Pointers

Linked List Cycle II (LC 142)(opens leetcode.com)

You're given the head of a linked list that may loop back on itself. Find and return the exact node where that loop begins — or null if the list has no loop at all.

(A test harness may describe the loop internally with a 0-indexed pos marking where the tail's next pointer reconnects, using -1 for no loop, but that index is never handed to your function.)

You must leave the linked list unmodified.

Example 1
Input:head = [3,2,0,-4], pos = 1
Output:tail connects to node index 1
The tail node (-4) loops back into the node at index 1 (value 2), so that node is the cycle's entry point.
320-4

Fast & Slow Pointers

slow (1x) vs fast (2x) (Velocity Differential)
Spot it when: A linked list (or an array used as next = nums[i]) may contain a loop, or you need its middle in one pass with O(1) extra space.
Fast & Slow Pointers
Python
1from typing import Optional
2 
3class ListNode:
4 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5 self.val = val
6 self.next = next
7 
8def find_cycle_entrance(head: Optional[ListNode]) -> Optional[ListNode]:
9 """Linked List Cycle II: Floyd's tortoise and hare, then find the cycle's first node.
10 Time: O(N) | Space: O(1)
11 """
12 if not head or not head.next:
13 return None
14 
15 slow = fast = head # both pointers start together; fast will take exactly twice slow's steps
16 has_cycle = False
17 
18 # 1. Phase 1 - Detect Collision:
19 # fast gains exactly one step on slow per move, so inside a cycle it cannot jump over slow: they must meet. Testing fast and fast.next (not just fast) keeps fast.next.next from reading an attribute of None.
20 while fast and fast.next:
21 slow = slow.next # 1 step
22 fast = fast.next.next # 2 steps
23 if slow == fast:
24 has_cycle = True
25 break
26 
27 if not has_cycle:
28 return None # fast fell off the end: no cycle
29 
30 # 2. Phase 2 - Locate Cycle Origin:
31 # head and the meeting point are the same number of steps from the cycle's first node, up to whole laps, so walking both one step at a time lands them on it together.
32 ptr1 = head
33 ptr2 = slow
34 while ptr1 != ptr2:
35 ptr1 = ptr1.next
36 ptr2 = ptr2.next
37 
38 return ptr1
Invariant: fast has always taken exactly twice as many steps as slow, so inside a cycle the gap between them shrinks by one on every move.

Inside a cycle, fast gains exactly one step on slow per move, so it can't jump over it: if there is a cycle they meet, and if fast runs off the end there is none (and slow is then at the middle).

Walking from the head and walking from the meeting point reach the cycle's first node after the same number of steps (the second walk may add whole laps), so ptr1 from head and ptr2 from the meeting point, one step at a time, both land on it.

The trap is the loop test: write while fast and fast.next, because fast.next.next crashes when fast.next is None; testing only fast crashes on every list without a cycle that has an odd number of nodes.

See it run

Fast & Slow Pointers, step by step

slow node 0 · fast node 0

slow node 1 · fast node 0

slow node 1 · fast node 2

slow node 2 · fast node 2

slow node 2 · fast node 1

slow node 3 · fast node 1

slow node 3 · fast node 3

slow node 3 · fast node 3

ptr1 node 0 · ptr2 node 3

ptr1 node 1 · ptr2 node 1

ptr1 node 1 · ptr2 node 1 · entrance 1

Each circle is a node and shows its value. An arrow points to the next node. Node 0 is the head, and a node number is its position in the list.

  • slow the pointer that takes 1 step
  • fast the pointer that takes 2 steps
  • ptr1 the walker that starts at the head
  • ptr2 the walker that starts at the meeting node
  • together two pointers on the same node

Start: slow and fast both stand on the head. Each loop, slow takes 1 step and fast takes 2.

slow takes 1 step, from node 0 to node 1 (value 2).

fast.next is node 1, so fast can take 2 steps and lands on node 2. The loop test must check fast and fast.next, or the last node of a list with no cycle crashes.

slow takes 1 step, from node 1 to node 2 (value 0).

fast takes 2 steps, from node 2 through node 3 to node 1.

slow takes 1 step, from node 2 to node 3 (value -4).

fast takes 2 steps, from node 1 through node 2 to node 3.

slow and fast meet on node 3, so the list has a cycle. Now start ptr1 at the head and ptr2 at the meeting node.

ptr1 is on node 0 and ptr2 is on node 3. Both will now take 1 step per move.

ptr1 moves from node 0 to node 1. ptr2 moves from node 3 to node 1.

ptr1 and ptr2 are on the same node, so node 1 (value 2) is where the cycle starts.

3together20-4
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing while fast instead of while fast and fast.next

    Mistake
    while fast:
    Fix
    while fast and fast.next:

    What happens: Each step moves fast two nodes with fast.next.next. When fast is the last node, fast.next is None. Then fast.next.next reads an attribute of None, and Python raises an AttributeError. This happens on every list without a cycle that has an odd number of nodes.

    Example: head = [1, 2, 3], pos = -1 (no cycle)
    Mistake: raises AttributeErrorFix: returns None
  2. 2

    Gotcha 2: Moving ptr2 two steps at a time in phase 2

    Mistake
    ptr2 = ptr2.next.next
    Fix
    ptr2 = ptr2.next

    What happens: head and the meeting point are the same number of steps from the cycle's first node, counting whole laps. That holds only when both pointers move one step at a time. With two steps for ptr2, the pointers can meet at another node.

    Example: head = [3, 2, 0, -4], pos = 1 (the cycle starts at the node 2)
    Mistake: returns -4Fix: returns 2
  3. 3

    Gotcha 3: Expecting slow to stop at the first of two middles

    What happens: Without a cycle, slow is at the middle when fast runs off the end. With an even number of nodes, there are two middles. Starting with slow = fast = head stops slow at the second one. Start fast at head.next to stop slow at the first one.

  4. 4

    Gotcha 4: Starting the pointers at an index other than 0

    What happens: Find the Duplicate Number (LC 287) reads the array as a linked list. Index i points to index nums[i]. Every value is between 1 and n, so no index points to index 0. So index 0 is outside the cycle, and the cycle's first node is the duplicate value. From another index, the walk can start inside the cycle and return a value that is not the duplicate.

Not this one if…
  • The array is declared circular, so you already know where the loop is and must look past the end, as in Next Greater Element II (LC 503), instead of finding out whether a loop exists. See Circular Array

Theory

The question we build

What we know

The input is a linked list, possibly with a cycle. fast moves two nodes for every one node slow moves, so inside a cycle the gap between them shrinks by exactly one node per move.

What the problem gives us

The input is head. Return the node where the cycle begins, or None when there is no cycle.

The question we build

We ask: "does slow ever equal fast?" If the list has no cycle, fast eventually reaches the end. If the list has a cycle, the gap between slow and fast shrinks by one every move, so it must reach zero before fast can leave the cycle.

Narrowing down to the template

Why can fast never jump over slow inside a cycle? The gap between them is a whole number of nodes, and it shrinks by exactly one node on every move, so it passes through every smaller value, including zero, before it could become negative. So fast cannot skip past slow without first landing on it: if a cycle exists, the two pointers meet inside it. That is exactly Fast & Slow Pointers: walk slow one step and fast two steps per move, and a meeting proves a cycle, a fast that falls off the end proves there is none.

Step 1: Track every node visited

Walk the list once, remembering every node seen. Correct: the first node visited twice is where the cycle starts. States the contract in one listing: find the first repeated node, or confirm there is none.

Step 1: Track every node visitedPython
1def find_cycle_entrance_brute(head):
2 seen = set()
3 node = head
4 while node:
5 if id(node) in seen:
6 return node # The first node visited twice is the cycle's first node
7 seen.add(id(node))
8 node = node.next
9 return None

Cost: O(N) time, O(N) extra space for the visited set.

Step 2: Detect the cycle without extra memory

fast gains exactly one step on slow per move, so it can never jump over slow inside a cycle: they must meet. So two pointers, no set, decide whether a cycle exists in O(1) space: walk slow by one node and fast by two, and stop on a meeting or on fast falling off the end. The loop test has to be while fast and fast.next, because fast.next.next fails the moment fast.next is None, which happens on every cycle-free list with an odd number of nodes.

Step 2: Detect the cycle without extra memoryPython
1def has_cycle(head):
2 if not head or not head.next:
3 return False
4 slow = fast = head
5 while fast and fast.next: # fast.next.next needs fast.next to exist
6 slow = slow.next # 1 step
7 fast = fast.next.next # 2 steps: fast gains exactly 1 step on slow per move
8 if slow == fast:
9 return True # fast could not jump over slow: they meet inside the cycle
10 return False # fast fell off the end: no cycle

Cost: O(N) time, O(1) extra space, but it only answers yes or no.

Step 3: Locate where the cycle starts

Detecting a cycle is not the same as finding where it starts. From the meeting point, fast has gone around the cycle exactly as many extra times as slow has made trips, and both have covered the same distance from head to the meeting point, up to whole laps of the cycle, so a second pair of one-step walkers, one from head and one from the meeting point, reaches the cycle's first node together.

Step 3: Locate where the cycle startsPython
1+New line: from typing import Optional
2
3+New line: class ListNode:
4+New line: def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5+New line: self.val = val
6+New line: self.next = next
7
8+New line: def find_cycle_entrance(head: Optional[ListNode]) -> Optional[ListNode]:
9+New line: """Linked List Cycle II: Floyd's tortoise and hare, then find the cycle's first node.
10+New line: Time: O(N) | Space: O(1)
11+New line: """
12 if not head or not head.next:
13+New line: return None
14
15 slow = fast = head # both pointers start together; fast will take exactly twice slow's steps
16+New line: has_cycle = False
17
18+New line: # 1. Phase 1 - Detect Collision:
19+New line: # fast gains exactly one step on slow per move, so inside a cycle it cannot jump over slow: they must meet. Testing fast and fast.next (not just fast) keeps fast.next.next from reading an attribute of None.
20 while fast and fast.next:
21 slow = slow.next # 1 step
22 fast = fast.next.next # 2 steps
23 if slow == fast:
24+New line: has_cycle = True
25+New line: break
26
27+New line: if not has_cycle:
28+New line: return None # fast fell off the end: no cycle
29
30+New line: # 2. Phase 2 - Locate Cycle Origin:
31+New line: # head and the meeting point are the same number of steps from the cycle's first node, up to whole laps, so walking both one step at a time lands them on it together.
32+New line: ptr1 = head
33+New line: ptr2 = slow
34+New line: while ptr1 != ptr2:
35+New line: ptr1 = ptr1.next
36+New line: ptr2 = ptr2.next
37
38+New line: return ptr1

Cost: O(N) time, O(1) extra space, and this time it returns the exact node.

BeforeAfterWhat changed
return True (Phase 1 only)has_cycle = True; break, then Phase 2's ptr1/ptr2 walk from head and the meeting pointA yes/no answer becomes the exact starting node, by walking two more one-step pointers until they meet.

Practice

Canonical LeetCode:Linked List Cycle II (LC 142)Linked List Cycle (LC 141)Find the Duplicate Number (LC 287)Middle of the Linked List (LC 876)Circular Array Loop (LC 457)Reorder List (LC 143)Happy Number (LC 202)
Theory Context•Two Pointers & Sliding Window
P1EasyMental model~7 min speedrun

Fast & Slow Pointers (Floyd's Tortoise and Hare)

Grows into
Fixed-Offset Pointers

Linked List Cycle II (LC 142)(opens leetcode.com)

You're given the head of a linked list that may loop back on itself. Find and return the exact node where that loop begins — or null if the list has no loop at all.

(A test harness may describe the loop internally with a 0-indexed pos marking where the tail's next pointer reconnects, using -1 for no loop, but that index is never handed to your function.)

You must leave the linked list unmodified.

Example 1
Input:head = [3,2,0,-4], pos = 1
Output:tail connects to node index 1
The tail node (-4) loops back into the node at index 1 (value 2), so that node is the cycle's entry point.
320-4

Fast & Slow Pointers

slow (1x) vs fast (2x) (Velocity Differential)
Spot it when: A linked list (or an array used as next = nums[i]) may contain a loop, or you need its middle in one pass with O(1) extra space.
Fast & Slow Pointers
Python
1from typing import Optional
2 
3class ListNode:
4 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5 self.val = val
6 self.next = next
7 
8def find_cycle_entrance(head: Optional[ListNode]) -> Optional[ListNode]:
9 """Linked List Cycle II: Floyd's tortoise and hare, then find the cycle's first node.
10 Time: O(N) | Space: O(1)
11 """
12 if not head or not head.next:
13 return None
14 
15 slow = fast = head # both pointers start together; fast will take exactly twice slow's steps
16 has_cycle = False
17 
18 # 1. Phase 1 - Detect Collision:
19 # fast gains exactly one step on slow per move, so inside a cycle it cannot jump over slow: they must meet. Testing fast and fast.next (not just fast) keeps fast.next.next from reading an attribute of None.
20 while fast and fast.next:
21 slow = slow.next # 1 step
22 fast = fast.next.next # 2 steps
23 if slow == fast:
24 has_cycle = True
25 break
26 
27 if not has_cycle:
28 return None # fast fell off the end: no cycle
29 
30 # 2. Phase 2 - Locate Cycle Origin:
31 # head and the meeting point are the same number of steps from the cycle's first node, up to whole laps, so walking both one step at a time lands them on it together.
32 ptr1 = head
33 ptr2 = slow
34 while ptr1 != ptr2:
35 ptr1 = ptr1.next
36 ptr2 = ptr2.next
37 
38 return ptr1
Invariant: fast has always taken exactly twice as many steps as slow, so inside a cycle the gap between them shrinks by one on every move.

Inside a cycle, fast gains exactly one step on slow per move, so it can't jump over it: if there is a cycle they meet, and if fast runs off the end there is none (and slow is then at the middle).

Walking from the head and walking from the meeting point reach the cycle's first node after the same number of steps (the second walk may add whole laps), so ptr1 from head and ptr2 from the meeting point, one step at a time, both land on it.

The trap is the loop test: write while fast and fast.next, because fast.next.next crashes when fast.next is None; testing only fast crashes on every list without a cycle that has an odd number of nodes.

See it run

Fast & Slow Pointers, step by step

slow node 0 · fast node 0

slow node 1 · fast node 0

slow node 1 · fast node 2

slow node 2 · fast node 2

slow node 2 · fast node 1

slow node 3 · fast node 1

slow node 3 · fast node 3

slow node 3 · fast node 3

ptr1 node 0 · ptr2 node 3

ptr1 node 1 · ptr2 node 1

ptr1 node 1 · ptr2 node 1 · entrance 1

Each circle is a node and shows its value. An arrow points to the next node. Node 0 is the head, and a node number is its position in the list.

  • slow the pointer that takes 1 step
  • fast the pointer that takes 2 steps
  • ptr1 the walker that starts at the head
  • ptr2 the walker that starts at the meeting node
  • together two pointers on the same node

Start: slow and fast both stand on the head. Each loop, slow takes 1 step and fast takes 2.

slow takes 1 step, from node 0 to node 1 (value 2).

fast.next is node 1, so fast can take 2 steps and lands on node 2. The loop test must check fast and fast.next, or the last node of a list with no cycle crashes.

slow takes 1 step, from node 1 to node 2 (value 0).

fast takes 2 steps, from node 2 through node 3 to node 1.

slow takes 1 step, from node 2 to node 3 (value -4).

fast takes 2 steps, from node 1 through node 2 to node 3.

slow and fast meet on node 3, so the list has a cycle. Now start ptr1 at the head and ptr2 at the meeting node.

ptr1 is on node 0 and ptr2 is on node 3. Both will now take 1 step per move.

ptr1 moves from node 0 to node 1. ptr2 moves from node 3 to node 1.

ptr1 and ptr2 are on the same node, so node 1 (value 2) is where the cycle starts.

3together20-4
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing while fast instead of while fast and fast.next

    Mistake
    while fast:
    Fix
    while fast and fast.next:

    What happens: Each step moves fast two nodes with fast.next.next. When fast is the last node, fast.next is None. Then fast.next.next reads an attribute of None, and Python raises an AttributeError. This happens on every list without a cycle that has an odd number of nodes.

    Example: head = [1, 2, 3], pos = -1 (no cycle)
    Mistake: raises AttributeErrorFix: returns None
  2. 2

    Gotcha 2: Moving ptr2 two steps at a time in phase 2

    Mistake
    ptr2 = ptr2.next.next
    Fix
    ptr2 = ptr2.next

    What happens: head and the meeting point are the same number of steps from the cycle's first node, counting whole laps. That holds only when both pointers move one step at a time. With two steps for ptr2, the pointers can meet at another node.

    Example: head = [3, 2, 0, -4], pos = 1 (the cycle starts at the node 2)
    Mistake: returns -4Fix: returns 2
  3. 3

    Gotcha 3: Expecting slow to stop at the first of two middles

    What happens: Without a cycle, slow is at the middle when fast runs off the end. With an even number of nodes, there are two middles. Starting with slow = fast = head stops slow at the second one. Start fast at head.next to stop slow at the first one.

  4. 4

    Gotcha 4: Starting the pointers at an index other than 0

    What happens: Find the Duplicate Number (LC 287) reads the array as a linked list. Index i points to index nums[i]. Every value is between 1 and n, so no index points to index 0. So index 0 is outside the cycle, and the cycle's first node is the duplicate value. From another index, the walk can start inside the cycle and return a value that is not the duplicate.

Not this one if…
  • The array is declared circular, so you already know where the loop is and must look past the end, as in Next Greater Element II (LC 503), instead of finding out whether a loop exists. See Circular Array

Theory

The question we build

What we know

The input is a linked list, possibly with a cycle. fast moves two nodes for every one node slow moves, so inside a cycle the gap between them shrinks by exactly one node per move.

What the problem gives us

The input is head. Return the node where the cycle begins, or None when there is no cycle.

The question we build

We ask: "does slow ever equal fast?" If the list has no cycle, fast eventually reaches the end. If the list has a cycle, the gap between slow and fast shrinks by one every move, so it must reach zero before fast can leave the cycle.

Narrowing down to the template

Why can fast never jump over slow inside a cycle? The gap between them is a whole number of nodes, and it shrinks by exactly one node on every move, so it passes through every smaller value, including zero, before it could become negative. So fast cannot skip past slow without first landing on it: if a cycle exists, the two pointers meet inside it. That is exactly Fast & Slow Pointers: walk slow one step and fast two steps per move, and a meeting proves a cycle, a fast that falls off the end proves there is none.

Step 1: Track every node visited

Walk the list once, remembering every node seen. Correct: the first node visited twice is where the cycle starts. States the contract in one listing: find the first repeated node, or confirm there is none.

Step 1: Track every node visitedPython
1def find_cycle_entrance_brute(head):
2 seen = set()
3 node = head
4 while node:
5 if id(node) in seen:
6 return node # The first node visited twice is the cycle's first node
7 seen.add(id(node))
8 node = node.next
9 return None

Cost: O(N) time, O(N) extra space for the visited set.

Step 2: Detect the cycle without extra memory

fast gains exactly one step on slow per move, so it can never jump over slow inside a cycle: they must meet. So two pointers, no set, decide whether a cycle exists in O(1) space: walk slow by one node and fast by two, and stop on a meeting or on fast falling off the end. The loop test has to be while fast and fast.next, because fast.next.next fails the moment fast.next is None, which happens on every cycle-free list with an odd number of nodes.

Step 2: Detect the cycle without extra memoryPython
1def has_cycle(head):
2 if not head or not head.next:
3 return False
4 slow = fast = head
5 while fast and fast.next: # fast.next.next needs fast.next to exist
6 slow = slow.next # 1 step
7 fast = fast.next.next # 2 steps: fast gains exactly 1 step on slow per move
8 if slow == fast:
9 return True # fast could not jump over slow: they meet inside the cycle
10 return False # fast fell off the end: no cycle

Cost: O(N) time, O(1) extra space, but it only answers yes or no.

Step 3: Locate where the cycle starts

Detecting a cycle is not the same as finding where it starts. From the meeting point, fast has gone around the cycle exactly as many extra times as slow has made trips, and both have covered the same distance from head to the meeting point, up to whole laps of the cycle, so a second pair of one-step walkers, one from head and one from the meeting point, reaches the cycle's first node together.

Step 3: Locate where the cycle startsPython
1+New line: from typing import Optional
2
3+New line: class ListNode:
4+New line: def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5+New line: self.val = val
6+New line: self.next = next
7
8+New line: def find_cycle_entrance(head: Optional[ListNode]) -> Optional[ListNode]:
9+New line: """Linked List Cycle II: Floyd's tortoise and hare, then find the cycle's first node.
10+New line: Time: O(N) | Space: O(1)
11+New line: """
12 if not head or not head.next:
13+New line: return None
14
15 slow = fast = head # both pointers start together; fast will take exactly twice slow's steps
16+New line: has_cycle = False
17
18+New line: # 1. Phase 1 - Detect Collision:
19+New line: # fast gains exactly one step on slow per move, so inside a cycle it cannot jump over slow: they must meet. Testing fast and fast.next (not just fast) keeps fast.next.next from reading an attribute of None.
20 while fast and fast.next:
21 slow = slow.next # 1 step
22 fast = fast.next.next # 2 steps
23 if slow == fast:
24+New line: has_cycle = True
25+New line: break
26
27+New line: if not has_cycle:
28+New line: return None # fast fell off the end: no cycle
29
30+New line: # 2. Phase 2 - Locate Cycle Origin:
31+New line: # head and the meeting point are the same number of steps from the cycle's first node, up to whole laps, so walking both one step at a time lands them on it together.
32+New line: ptr1 = head
33+New line: ptr2 = slow
34+New line: while ptr1 != ptr2:
35+New line: ptr1 = ptr1.next
36+New line: ptr2 = ptr2.next
37
38+New line: return ptr1

Cost: O(N) time, O(1) extra space, and this time it returns the exact node.

BeforeAfterWhat changed
return True (Phase 1 only)has_cycle = True; break, then Phase 2's ptr1/ptr2 walk from head and the meeting pointA yes/no answer becomes the exact starting node, by walking two more one-step pointers until they meet.

Practice

Canonical LeetCode:Linked List Cycle II (LC 142)Linked List Cycle (LC 141)Find the Duplicate Number (LC 287)Middle of the Linked List (LC 876)Circular Array Loop (LC 457)Reorder List (LC 143)Happy Number (LC 202)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•