Beginner: foundations · Stacks, queues and linked lists · topic 20 of 61 · 40 min
Fast and slow pointers and merging lists
Explanation
Two pointers on a linked list
Module 3 used two pointers on arrays. On a linked list you cannot jump to an index, but you can walk two pointers at different speeds, or keep a fixed gap between them. That answers questions about the middle, cycles and the end of the list in one pass, with O(1) extra space.
The middle of a list
Move slow one step and fast two steps at a time. When fast reaches the end, slow has gone half as far, so it is at the middle. With the loop condition while fast and fast.next, an even-length list gives the SECOND of its two middle nodes: for 1 → 2 → 3 → 4 it stops at 3. Write while fast.next and fast.next.next instead to stop at the first middle, 2; that version is used to split a list in half for merge sort.
def middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# 1 -> 2 -> 3 -> 4 -> 5 gives the node 3
# 1 -> 2 -> 3 -> 4 gives the node 3 (the second middle)Detecting a cycle: Floyd's tortoise and hare
A cycle means some node's next points back to an earlier node, so walking never reaches None. A plain loop like while node: node = node.next then runs forever. One fix is a set of visited nodes, which costs O(n) extra space. Floyd's algorithm uses O(1): move slow one step and fast two steps. Without a cycle, fast reaches the end. With a cycle, both pointers end up inside it, and each step fast gains exactly one node on slow, so the gap shrinks to zero and they meet.
Compare nodes with slow is fast, not their values: two different nodes can hold the same value.
Where the cycle starts
After slow and fast meet, put one pointer back at the head and move both ONE step at a time. They meet again exactly at the first node of the cycle. Why: let a be the distance from the head to the cycle start, b the distance from the start to the meeting point, and c the cycle length. Fast walked twice as far as slow: 2(a + b) = a + b + k·c for some whole number of laps k, so a = k·c − b. Walking a steps from the meeting point therefore lands on the cycle start, which is also where a pointer from the head arrives after a steps.
def cycle_start(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast: # they met inside the cycle
p = head
while p is not slow:
p = p.next
slow = slow.next
return p # first node of the cycle
return None # fast reached the end: no cycleThe n-th node from the end
To remove the n-th node from the end in one pass, keep a gap of n nodes between two pointers. Start both at a dummy node before the head and move fast n + 1 steps ahead. Then move both one step at a time until fast is None. Now slow sits just BEFORE the node to remove, so slow.next = slow.next.next deletes it. The dummy covers the case where the head itself is removed (n equal to the length).
Merging two sorted lists with a dummy node
The array merge from Module 3 works on lists too, without copying values: relink the existing nodes. A dummy node gives the merged list a fixed start, and a tail pointer marks where the next node goes. When one list runs out, attach the rest of the other in one step, because it is already sorted. O(n + m) time and O(1) extra space.
dummy = tail = ListNode()
while a and b:
if a.val <= b.val:
tail.next, a = a, a.next
else:
tail.next, b = b, b.next
tail = tail.next
tail.next = a or b # attach whatever is left
merged = dummy.nextPalindrome linked list
Is 1 → 2 → 2 → 1 the same backwards? Copying the values into a Python list and comparing it with its reverse works in O(n) time but uses O(n) extra space. The O(1)-space way combines this module's tools: find the middle with slow and fast pointers, reverse the second half (Topic 4), then walk from the head and from the reversed half together, comparing values. Reverse the second half back afterwards if the caller still needs the list.
Common mistakes
- Writing while fast.next and fast, which crashes when fast is None. Fix: while fast and fast.next (check fast first).
- Comparing values instead of nodes when looking for a cycle. Fix: slow is fast.
- An off-by-one gap in n-th from the end. Fix: start at a dummy and move fast n + 1 steps.
- Forgetting to attach the leftover part after merging. Fix: tail.next = a or b.
- Not saying which middle you return for even lengths. Fix: state it, then choose the loop condition to match.
- Printing or converting a list that may have a cycle, which never ends. Fix: detect the cycle first.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Fast and slow pointers and merging lists · 1/8
This lesson uses two pointers on linked lists, and a dummy node for merging.
Prefer a video by a teacher? Find free videos on “fast and slow pointers linked list cycle detection floyd middle of linked list merge two sorted lists” on YouTube ↗
In real life: Merging statements from two branches
A bank keeps each branch's transactions sorted by time. To show a customer one combined statement, it merges the sorted streams instead of sorting everything again: always take the earlier of the two next entries. That is the merge of two sorted lists, O(n + m). Python's heapq.merge does this lazily for any number of sorted inputs.
import heapq
branch_a = [("09:05", "A: deposit"), ("10:30", "A: withdrawal")]
branch_b = [("09:40", "B: cheque"), ("11:15", "B: deposit")]
for time, entry in heapq.merge(branch_a, branch_b):
print(time, entry)Practice (0/4 done)
Write middle_node(head) that returns the middle NODE of a linked list (the node itself, not its value) using slow and fast pointers in one pass. For an even number of nodes, return the SECOND of the two middle nodes: for 1 -> 2 -> 3 -> 4 return the node holding 3. Return None for an empty list.
Write has_cycle(head) that returns True if the linked list has a cycle (some node's next points back to an earlier node), else False. Use Floyd's slow and fast pointers: O(n) time and O(1) extra space (no set of visited nodes). Careful: a plain "while node: node = node.next" loop never ends on a list with a cycle.
Write merge_two_lists(l1, l2) that merges two linked lists, each sorted in non-decreasing order, into one sorted list and returns its head. Relink the existing nodes (do not create new value nodes; a dummy node is fine). O(n + m) time, O(1) extra space. Example: 1 -> 2 -> 4 and 1 -> 3 -> 4 give 1 -> 1 -> 2 -> 3 -> 4 -> 4.
Write remove_nth_from_end(head, n) that removes the n-th node from the END of the list (n = 1 is the last node) and returns the head. 1 <= n <= length. Do it in ONE pass with two pointers n nodes apart, starting from a dummy node. Example: 1 -> 2 -> 3 -> 4 -> 5 with n = 2 becomes 1 -> 2 -> 3 -> 5.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Monotonic stacks and queuesStacks, queues and linked lists · same module
- Queues and dequesStacks, queues and linked lists · same module
- Singly linked listsC Programming (from zero to systems) · Data structures in C
- Branching, merging and conflictsDevOps: Git, Docker, Kubernetes and CI/CD · Git in depth
- Binary search treesTrees and binary search trees
- Arrays: traversal and in-place updatesArrays and strings
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
With while fast and fast.next, which node does slow stop at for 10 → 20 → 30 → 40?
Answer: 30
For an even length this loop gives the second middle: slow moves to 20, then 30, while fast moves to 30, then off the end.
What is the extra space of Floyd's cycle detection?
Answer: O(1)
Only two pointers, compared with a set of visited nodes, which is O(n).
After slow and fast meet inside a cycle, how do you find the first node of the cycle?
Answer: Move one pointer back to the head and step both one node at a time until they meet
Because a = k·c − b, both pointers reach the cycle start after a steps.
Why compare slow is fast rather than slow.val == fast.val?
Answer: Two different nodes can hold the same value
Equal values do not mean the same node; [7, 7, 7] has no cycle.
When merging two sorted lists, one list runs out. What do you do with the other?
Answer: Attach its remaining part in one step: tail.next = a or b
The remaining part is already sorted and larger than everything merged so far.
What problem does a dummy node solve?Related
Answer: Deleting or inserting at the head no longer needs special code
With a node before the head, every real node has a predecessor; return dummy.next at the end.
From Linked lists
Why does push_front take Node **head?Related
Answer: To change the caller's head pointer
The function must update the caller's pointer.
From Singly linked lists
Merging sorted arrays of sizes n and m with two pointers costs…Related
Answer: O(n + m)
Each step moves one pointer forward, so there are n + m steps in total.
From Two pointers
You fixed all conflicts in two files. What is the next step?Related
Answer: git add the files, then git commit
Staging the files marks them resolved; the commit completes the merge. --abort would throw your work away.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Stacks, queues and linked lists”. The module exam comes next.
- Module 4 exam: Stacks, queues and linked lists · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Recursion and backtracking. 61 topics left in the course before the final exam.