Pattern 06 of 20
Linked List
Rewire pointers carefully: dummy heads, fast and slow pointers, in-place reversal.
When to use it
- The input is a linked list and random access is not available
- Finding a middle, a cycle or the k-th node from the end
- Reversing, merging or reordering nodes without extra memory
The idea
Most list problems are about changing next pointers in the right order so nothing is lost. A dummy head removes special cases at the front, and two pointers moving at different speeds find middles and cycles.
Typical cost O(n) time, O(1) extra space.
How it works
A linked list gives you no random access, so every algorithm is a walk. The work is in rewiring next pointers in an order that never loses the rest of the list. Before you overwrite a pointer, save what it pointed to.
A dummy head is the cheapest trick in the pattern. It gives the real head a predecessor, so deleting or inserting at the front looks exactly like doing it in the middle. Return dummy.next at the end.
Fast and slow pointers find structure without extra memory. When the fast pointer moves two steps for each step of the slow one, the slow pointer is at the middle when the fast one reaches the end, and the two meet inside a cycle if one exists. A gap of k nodes between two pointers finds the k-th node from the end.
Many medium problems are compositions of three primitives: find the middle, reverse a sublist, merge two lists. Reorder, palindrome check and k-group reversal are all built from them.
How to attack it
- 1Draw three or four nodes and the pointers before writing code.
- 2Add a dummy head if the head itself might change.
- 3Save next before overwriting any pointer.
- 4Break the problem into find-middle, reverse and merge if it looks complicated.
- 5Test with an empty list, one node, and two nodes.
Template
A skeleton to adapt, not a solution to paste. Write it from memory a few times.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse(head):
prev = None
while head:
nxt = head.next # save the rest before rewiring
head.next = prev
prev, head = head, nxt
return prev
def middle(head):
# Slow ends at the middle (second middle for even lengths).
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def remove_value(head, val):
dummy = ListNode(0, head) # gives the head a predecessor
prev = dummy
while prev.next:
if prev.next.val == val:
prev.next = prev.next.next
else:
prev = prev.next
return dummy.nextVariants
- In-place reversal
- Whole list, a sublist between positions, or every group of k nodes, always with prev, curr and a saved next.
- Fast and slow pointers
- Middle of the list, cycle detection, and the cycle entry (reset one pointer to the head and step both by one).
- Gap pointers
- Advance one pointer k steps first, then move both; the trailing one stops just before the k-th node from the end.
- Merging
- Splice the smaller head onto a tail pointer behind a dummy; extends to k lists with a heap.
- Copy with extra pointers
- Deep-copying a list with random pointers needs a map from old node to new, or interleaved copies.
Pitfalls
- Losing the rest of the list by overwriting next before saving it.
- Special-casing the head instead of using a dummy, and getting one branch wrong.
- Dereferencing fast.next.next without checking fast.next first.
- Forgetting to cut the tail (set next to null) after splitting a list, which creates a cycle.
- Returning the old head after a reversal instead of the new one.
What it costs
| Operation | Cost |
|---|---|
| Access the k-th node | O(k) |
| Insert or delete with a known predecessor | O(1) |
| Reverse, find middle, detect cycle | O(n) time, O(1) space |
| Merge two sorted lists | O(n + m) time, O(1) space |
31 Linked List problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Reverse the ChainFreeEasy
- 2Midpoint of the ChainFreeEasy
- 3Weave Two Sorted ChainsFreeEasy
- 4Collapse Repeated ReadingsEasy
- 5Drop Matching NodesEasy
- 6Chain Reads the Same Both WaysEasy
- 7Binary Chain to NumberEasy
- 8Loop DetectorEasy
- 9Drop Every K-th NodeEasy
- 10Alternate Two ChainsEasy
- 11Loop Entry IndexMedium
- 12Cut the K-th From the TailMedium
- 13Odd Slots Then Even SlotsMedium
- 14Add Reversed Digit ChainsMedium
- 15Rotate the Chain RightMedium
- 16Fold the ChainMedium
- 17Pivot Partition of a ChainMedium
- 18Reverse a StretchMedium
- 19Add Forward Digit ChainsMedium
- 20Swap Neighbor PairsMedium
- 21Purge Duplicated ValuesMedium
- 22Twin Sum PeakMedium
- 23Drop the Middle NodeMedium
- 24Split a Chain Into K PartsMedium
- 25Sort a ChainMedium
- 26Flip in Blocks of KHard
- 27Merge Many Sorted ChainsHard
- 28Flip Blocks From the TailHard
- 29Growing Groups, Even FlipHard
- 30Nearly Sorted ChainHard
- 31Multiply Digit ChainsHard
Solve them in the browser, in Python or JavaScript.