Linked Lists Pointer Surgery
Eleven drills on the data structure that punishes sloppy pointer handling. Every problem here reduces to four moves โ a dummy head, fast & slow runners, in-place reversal, and gap pointers โ applied with discipline about which link you overwrite and when. Each drill has a traced diagram, a complete Java program and the bugs interviewers watch for.
What a Linked List Really Is
A singly linked list is a chain of nodes where each node stores a value and a single reference, next, to the node after it. The list itself is nothing more than a reference to the first node (head); the last node points to null. There is no index and no length field: to reach the k-th node you must walk k links from the head, which makes random access O(n). In exchange, inserting or deleting a node once you hold the node before it is O(1) โ you only rewrite one or two references, nothing is shifted.
That trade-off defines every interview problem in this chapter. You are almost never asked to search; you are asked to rewire: reverse links, splice two lists, cut a list in two, skip a node. The difficulty is not the algorithm โ it is keeping track of which references are still reachable while you overwrite them. Lose the only pointer to the rest of the list and the nodes are gone (garbage-collected in Java, leaked in C). The four tools in Chapter 03 exist to make that bookkeeping mechanical.
| Operation | Array / ArrayList | Singly linked list | Why |
|---|---|---|---|
| Access k-th element | O(1) | O(k) | must follow k next links |
| Insert / delete at head | O(n) | O(1) | array shifts every element; list rewrites head |
| Insert / delete after a known node | O(n) | O(1) | one or two reference writes |
| Find length | O(1) | O(n) | no size field on a bare ListNode |
| Extra memory per element | 0 | 1 reference | the next pointer |
How to Think About Pointer Problems
curr.next destroys the only path to the rest of the listnext = curr.next before you rewrite it. Reversal, swapping and partitioning all start with this line.Four Templates That Solve Everything
Four templates cover all eleven drills. Learn them as muscle memory โ in an interview you should be able to write each one without thinking, and spend your attention on the problem-specific twist.
- Draw three nodes on paper and write the reference assignments in order โ the order is the algorithm.
- Ask: does the head change? If yes, add a dummy node.
- Ask: what is the loop condition for an empty list, one node, and two nodes? Test all three mentally.
- Compare nodes with
==(identity) when the question is "same node", never byval.
Reverse Linked List
head of a singly linked list, reverse the list and return the new head.curr.next erases your route forward, so save it first. Three references (prev, curr, next) are all you ever need.
The brute-force approach copies values into an array (or a stack) and writes them back in reverse โ O(n) time but O(n) extra space, and it changes values rather than structure, which interviewers usually reject. The optimal approach rewires each link in place in a single pass: O(n) time, O(1) space. A recursive version is elegant (head.next.next = head) but uses O(n) stack and overflows on lists of ~104+ nodes with default JVM settings.
| Iter | next saved | curr.next = prev | prev | curr | Reversed part |
|---|---|---|---|---|---|
| 1 | 2 | 1 โ null | 1 | 2 | 1 |
| 2 | 3 | 2 โ 1 | 2 | 3 | 2 โ 1 |
| 3 | 4 | 3 โ 2 | 3 | 4 | 3 โ 2 โ 1 |
| 4 | 5 | 4 โ 3 | 4 | 5 | 4 โ 3 โ 2 โ 1 |
| 5 | null | 5 โ 4 | 5 | null | 5 โ 4 โ 3 โ 2 โ 1 |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Copy to array / stack | O(n) | O(n) | rewrites values, not links |
| Iterative three-pointer | O(n) | O(1) | interview answer |
| Recursive | O(n) | O(n) stack | risk of StackOverflowError on long lists |
- Forgetting to save
curr.nextbefore overwriting it โ the rest of the list becomes unreachable. - Returning
headinstead ofprev: after the loop the old head is the tail. - Recursive version: forgetting
head.next = nullcreates a 2-node cycle between the first two nodes. - Empty list and single node must work without special cases โ the loop simply runs 0 or 1 times.
Merge Two Sorted Lists
l1 and l2, splice their nodes into one sorted list and return its head.Brute force: collect all values into an array, sort, rebuild โ O((n+m) log(n+m)) time and O(n+m) space, and it ignores that the inputs are already sorted. The optimal merge compares only the two fronts, so each node is touched once: O(n+m) time. Because we relink existing nodes instead of allocating new ones, extra space is O(1). A recursive merge is shorter but costs O(n+m) stack.
| Step | l1 | l2 | Compare | Append | Merged so far |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 <= 1 | l1:1 | 1 |
| 2 | 2 | 1 | 2 > 1 | l2:1 | 1,1 |
| 3 | 2 | 3 | 2 <= 3 | l1:2 | 1,1,2 |
| 4 | 4 | 3 | 4 > 3 | l2:3 | 1,1,2,3 |
| 5 | 4 | 4 | 4 <= 4 | l1:4 | 1,1,2,3,4 |
| 6 | null | 4 | loop ends | splice l2 | 1,1,2,3,4,4 |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Collect, sort, rebuild | O((n+m) log(n+m)) | O(n+m) | ignores sortedness |
| Iterative merge | O(n+m) | O(1) | relinks existing nodes |
| Recursive merge | O(n+m) | O(n+m) stack | concise but deep recursion |
- Forgetting
tail = tail.nextafter appending โ every node overwrites the same slot. - Looping until both lists are null and dereferencing the null one; stop when either is null and splice the remainder.
- Returning
dummyinstead ofdummy.nextadds a phantom 0 at the front. - Using
<instead of<=still sorts correctly but is not stable โ matters when nodes carry satellite data.
Cycle Detection & Middle Node
head, return true if some node can be reached again by continuously following next (the list contains a cycle), otherwise false. pos in the examples only describes the input; it is not passed to your function.null.
The obvious solution stores every visited node in a HashSet<ListNode> and reports a cycle on the first repeat โ O(n) time but O(n) space. Floyd's algorithm replaces the set with a second pointer moving at double speed: O(n) time (slow enters the cycle within n steps and is caught within one more lap) and O(1) space.
| Approach | Time | Space | Notes |
|---|---|---|---|
| HashSet of visited nodes | O(n) | O(n) | simple, extra memory |
| Floyd tortoise & hare | O(n) | O(1) | interview answer |
- Checking
slow == fastbefore moving โ both start at head, so it returns true immediately. - Loop guard must be
fast != null && fast.next != null, in that order, orfast.next.nextthrows NPE. - Comparing
slow.val == fast.valโ duplicate values give false positives; compare references. - Follow-up (LC 142): to find the cycle entry, reset one pointer to head after the meeting and advance both by 1; they meet at the entry.
head of a singly linked list, return the middle node. If there are two middle nodes, return the second one.The two-pass approach counts the length n, then walks n/2 steps โ O(n) time, O(1) space, perfectly acceptable but two traversals. The runner technique does it in one pass with the same complexity. The real value is that it composes: palindrome check, reorder list and merge sort on lists all start with "find the middle".
| Approach | Time | Space | Notes |
|---|---|---|---|
| Count length, then walk n/2 | O(n) | O(1) | two passes |
| Fast & slow | O(n) | O(1) | one pass |
| Copy into ArrayList | O(n) | O(n) | unnecessary memory |
- Want the first middle for even lengths (needed to split a list for merge sort)? Use
while (fast.next != null && fast.next.next != null). - Starting fast at
head.nextsilently switches which middle you get โ be deliberate. - A single node is its own middle; an empty list returns null โ both handled by the loop guard.
Remove Nth Node From End
head of a linked list, remove the n-th node from the end and return the head. 1 <= n <= length.null. Put two pointers n+1 links apart and slide them together: when the leader falls off the end, the follower is parked on the node before the one to delete โ exactly where you need to be to unlink it. Starting both at a dummy node makes deleting the real head an ordinary case.
Two-pass: count the length L, then walk L-n steps and unlink โ O(L) time, O(1) space, but two traversals and an extra head special case. The one-pass gap technique keeps the same bounds while touching each node at most twice. Interviewers often add "in one pass" specifically to push you toward the gap pointers.
| Phase | fast | slow | Note |
|---|---|---|---|
| open gap (3 moves) | Dโ1โ2โ3 | D | gap = n + 1 links |
| slide 1 | 4 | 1 | |
| slide 2 | 5 | 2 | |
| slide 3 | null | 3 | stop |
| unlink | โ | 3 | 3.next = 5 (node 4 removed) |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Two passes (length, then walk) | O(L) | O(1) | needs head special case without dummy |
| Dummy + gap, one pass | O(L) | O(1) | interview answer |
| Store nodes in a list | O(L) | O(L) | index L-n-1 directly |
- Gap of n instead of n+1 leaves slow on the target, and you cannot unlink a node from itself in a singly linked list.
- Without a dummy, removing the head (n == length) needs a separate branch โ and is the most frequently failed test case.
- Moving fast n+1 times from
head(not dummy) throws NPE when n == length.
Add Two Numbers
Converting each list to an int/long, adding, and converting back fails as soon as the numbers exceed 19 digits (lists can have 100 nodes). BigInteger works but sidesteps the exercise. Digit-by-digit addition with a carry is O(max(n, m)) time; the output list has max(n, m) + 1 nodes at most, and apart from it only O(1) extra space is used.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Convert to long, add, convert back | O(n+m) | O(1) | overflows beyond 19 digits โ wrong |
| BigInteger | O(n+m) | O(n+m) | correct, but avoids the point |
| Digit simulation with carry | O(max(n,m)) | O(1) extra | output list not counted |
- Stopping when both lists end and forgetting the last carry: 5 + 5 must return [0,1], not [0].
- Advancing
l1 = l1.nextwhenl1is already null โ guard each list separately. - Variant (LC 445) stores digits most-significant first: reverse both lists first, or use two stacks.
Swap Nodes in Pairs
prev on the node just before the pair; a dummy gives the first pair an anchor too. After the swap, the old first is the pair's tail and becomes the next anchor.
Swapping val fields is trivial but explicitly forbidden. The recursive solution (second.next = swapPairs(second.next)) is short but uses O(n) stack. The iterative rewire visits each node once with four assignments per pair: O(n) time, O(1) space.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Swap values | O(n) | O(1) | not allowed by the problem |
| Recursive | O(n) | O(n) stack | elegant, deep recursion |
| Iterative with dummy | O(n) | O(1) | interview answer |
- Wrong assignment order loses node 3 โ write the four lines on paper with three boxes first.
- Advancing
prev = secondinstead ofprev = first: after the swap,firstis the later node. - Guard must check both
prev.nextandprev.next.nextto leave an odd tail untouched. - Generalisation (LC 25, Reverse Nodes in k-Group) uses the same anchor idea with Template C on each block.
Rotate List
head of a linked list, rotate the list to the right by k places. k can be much larger than the length (up to 2ยท109).length - k - 1 steps from the old head (after reducing k %= length).
Rotating one step at a time (move the tail to the front, k times) costs O(nยทk) โ hopeless for k = 2ยท109. Measuring the length first lets you reduce k modulo n and locate the cut directly: one pass to measure, at most one more to the cut, O(n) time and O(1) space.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Move tail to front k times | O(nยทk) | O(1) | time-limit exceeded |
| Measure, ring, cut | O(n) | O(1) | k reduced mod n |
- Not reducing
k %= lengthโ walks billions of steps around the ring. - When
k % length == 0, return immediately; otherwise you close the ring and cut it in the same place, which works, but forgetting to cut creates a cycle. - Off-by-one on the walk: the new tail is reached after
length - k - 1steps from head, the new head is the node after it. - Empty list: computing
k % 0throws ArithmeticException โ guardhead == nullfirst.
Odd Even Linked List
evenHead), because at the end the odd chain's tail must point to it.
Copying odd-position values, then even-position values, into a new list uses O(n) space and violates the constraint. The weave rewires in place: every iteration advances both chains by one node, so the loop runs about n/2 times โ O(n) time, O(1) space.
| Iter | odd.next = | odd | even.next = | even | Guard after |
|---|---|---|---|---|---|
| 1 | 3 | 3 | 4 | 4 | even=4, even.next=5 โ continue |
| 2 | 5 | 5 | null | null | even = null โ stop |
| end | evenHead (2) | โ | โ | โ | 1โ3โ5โ2โ4 |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Two new lists / array copy | O(n) | O(n) | violates O(1) space |
| In-place weave | O(n) | O(1) | one pass |
- Confusing positions with values: [2,1,3,โฆ] groups by index, not by parity of the number.
- Losing
evenHeadโ onceevenadvances, the start of the even chain is unreachable. - Loop guard
even != null && even.next != nullhandles both odd and even lengths; checking onlyodd.nextbreaks one of them.
Intersection of Two Linked Lists
null. The lists must keep their original structure; there are no cycles.null between the two laps is what makes the "no intersection" case terminate.
Brute force compares every node of A with every node of B: O(nยทm). A HashSet of A's nodes gives O(n+m) time but O(n) space. Length-difference alignment (advance the longer list by |n-m|, then walk together) is O(n+m)/O(1) but needs two counting passes. The head-switching trick gets the same bounds with no counting and five lines of code.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Nested loops | O(nยทm) | O(1) | |
| HashSet of A's nodes | O(n+m) | O(n) | |
| Align by length difference | O(n+m) | O(1) | two counting passes |
| Switch heads | O(n+m) | O(1) | at most 2 laps each |
- Comparing
valinstead of references โ the example deliberately contains equal values (1) on different nodes. - Switching to the other head when
p.next == null(skipping the null step) loops forever when there is no intersection. - Modifying the lists (e.g. marking visited nodes) is not allowed โ the structure must be restored.
Partition List
x, rearrange it so that all nodes with value less than x come before nodes with value greater than or equal to x. Preserve the original relative order within each group.An array-based approach copies values out, stably partitions, and writes them back: O(n) time, O(n) space. Swapping-based in-place partition (Lomuto/Hoare) is not stable. Two chains reuse the existing nodes in a single pass: O(n) time, O(1) extra space, and stable by construction.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Copy values, partition, write back | O(n) | O(n) | |
| Two dummy chains | O(n) | O(1) | stable, one pass |
- Forgetting
ge.next = null: the last >= node still points at an old successor, often creating a cycle (here 5 โ 2). - Using
<=for the left chain โ the spec puts values equal to x on the right. - Returning
lessHeadinstead oflessHead.next; if no node is < x,lessHead.nextcorrectly becomes the ge chain.
All Eleven Drills at a Glance
Every drill on this page runs in linear time and โ except for the newly created digits of Add Two Numbers โ constant extra space. That is the signature of linked-list problems: the interviewer is not testing algorithmic cleverness so much as your ability to rewire references without losing any. If your solution allocates an array or a HashMap, there is almost always an O(1)-space pointer version they are waiting for.
| # | Drill | Pattern | Time | Space | Difficulty |
|---|---|---|---|---|---|
| 40 | Reverse Linked List | in-place reversal | O(n) | O(1) | Easy |
| 41 | Merge Two Sorted Lists | dummy + tail | O(n+m) | O(1) | Easy |
| 42 | Linked List Cycle | fast & slow | O(n) | O(1) | Easy |
| 43 | Middle of the Linked List | fast & slow | O(n) | O(1) | Easy |
| 44 | Remove Nth Node From End | dummy + gap | O(n) | O(1) | Medium |
| 45 | Add Two Numbers | simulation + dummy | O(max(n,m)) | O(1) extra | Medium |
| 46 | Swap Nodes in Pairs | dummy + rewiring | O(n) | O(1) | Medium |
| 47 | Rotate List | length + ring + cut | O(n) | O(1) | Medium |
| 48 | Odd Even Linked List | split & weave | O(n) | O(1) | Medium |
| 49 | Intersection of Two Lists | pointer switching | O(n+m) | O(1) | Easy |
| 50 | Partition List | two dummy chains | O(n) | O(1) | Medium |
- Dummy node whenever the head can change or you are building output (41, 44, 45, 46, 50).
- Fast & slow for middle and cycle (42, 43) โ guard with
fast != null && fast.next != null. - Gap pointers / length first for positions measured from the end (44, 47).
- Save next before you overwrite it โ reversal (40), swapping (46), weaving (48).
- Compare references, not values for "same node" questions (42, 49).
- Terminate output chains with
tail.next = nullto avoid stale links and cycles (47, 50).
- Combine templates: Palindrome Linked List (middle + reverse second half + compare), Reorder List (middle + reverse + weave), Sort List (middle + merge = merge sort), Linked List Cycle II (Floyd + reset to head).
- Review the data structure itself on the Linked List guide and the runner technique in Two Pointers.
- Full single-problem write-ups: LC 206, LC 21, LC 141, LC 19, LC 2; more practice in the LeetCode linked-list set.
- Next page: 06 Binary Trees & BST โ the same pointer discipline, but every node has two
nextreferences.