Pointers, dummies, and the middle
A list is a chain of arrows. Reverse flips the arrows with three names. Merge walks two already-sorted chains behind a dummy. Fast/slow finds the middle without counting n. Job 10454435 still none.
← Prefix Sum · Stacks & Queues →
ListNode with int val and ListNode next. Job 10454435 still has no public live-round list question. The notebook rows are other loops.
class ListNode {
int val;
ListNode next;
ListNode(int v) { val = v; }
}
Reverse a linked list — iterative three pointers
Recognize it from — reverse the whole list, reverse a sublist, reverse in groups of k, or “the old head becomes the tail.” If they say “do it in place, O(1) extra,” they want the three-pointer walk, not a stack of nodes.
What it actually does — each node currently points forward. You flip that arrow so it points at the node you just left. You cannot overwrite cur.next until you have saved the rest of the list, or you lose the chain. Three names: prev is the reversed prefix, cur is the node you are flipping, nxt is the still-forward suffix.
When the loop ends, cur is null and prev sits on the original last node — that is the new head. The original head variable still names the old first node, which now has next == null (it is the tail). Return prev, not the old head.
Simple — read this first
ListNode reverseList(ListNode head) {
ListNode prev = null; // reversed prefix; empty at the start
ListNode cur = head; // node whose .next we are about to flip
while (cur != null) { // stop when the unreversed suffix is gone
ListNode nxt = cur.next; // save the rest — we are about to overwrite .next
cur.next = prev; // the actual reverse: this arrow now points backward
prev = cur; // the prefix grows by the node we just flipped
cur = nxt; // walk onto the saved suffix
}
return prev; // prev is the original tail = new head
}
Interview — type this
ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head; // 0 or 1 node: already reversed
ListNode newHead = reverseList(head.next); // reverse the suffix; newHead is the old tail
head.next.next = head; // the old successor now points back at us
head.next = null; // we become the tail of this suffix (cut the old forward link)
return newHead; // bubble the new head all the way up
}
Example 1 — simple
Input 1 → 2 → 3. Iterative loop only.
| After iter | nxt | prev | cur | List you can see |
|---|---|---|---|---|
| before loop | — | null | 1 | 1 → 2 → 3 |
| 1 | 2 | 1 | 2 | 1 | 2 → 3 (1.next is null) |
| 2 | 3 | 2 | 3 | 2 → 1 | 3 |
| 3 | null | 3 | null | 3 → 2 → 1 |
Answer: return prev = node 3. The list is 3 → 2 → 1.
Example 2 — every step
Input 1 → 2 → 3 → 4. Same iterative code. head still names node 1 the whole time; it is not the pointer you return.
| Iter | nxt saved | cur.next = prev | then prev | then cur | head (node 1) .next | Picture |
|---|---|---|---|---|---|---|
| start | — | — | null | 1 | 2 | 1 → 2 → 3 → 4 |
| 1 | 2 | 1.next = null | 1 | 2 | null | 1 | 2 → 3 → 4 |
| 2 | 3 | 2.next = 1 | 2 | 3 | null | 2 → 1 | 3 → 4 |
| 3 | 4 | 3.next = 2 | 3 | 4 | null | 3 → 2 → 1 | 4 |
| 4 | null | 4.next = 3 | 4 | null | null | 4 → 3 → 2 → 1 |
Answer: return prev = node 4. Recursion on the same input: reverse(4) returns 4; then 3.next.next = 3 and 3.next = null gives 4 → 3; then 2 attaches behind 3; then 1 attaches behind 2. Same list, O(n) stack frames.
- Forgetting to save
nxtbeforecur.next = prev— the rest of the list is gone. - Returning the original
headafter the loop. That node is now the tail. - Recursive version: forgetting
head.next = null. You leave a cycle 1 ⇄ 2.
| Problem | Pattern | Links |
|---|---|---|
| Reverse in groups of k | Dummy + reverse a window of k, leftover policy asked out loud | R4 coding |
Merge two sorted lists — dummy head
Recognize it from — two (or k) lists already sorted; emit one sorted list. Merge k is the same idea with a heap of heads. Max-sum switching lists walks two sorted lists and jumps at shared values — dummy + two pointers still show up.
What it actually does — at every step you hold the remaining head of list A and list B. Whichever value is smaller (or equal — pick a consistent side) gets appended to the answer, and that list advances. A dummy node sits in front of the answer so the first append is identical to every later append. tail always names the last node already in the answer. When one list runs out, splice the rest of the other — it is already sorted.
Simple — read this first
ListNode mergeTwo(ListNode a, ListNode b) {
ListNode dummy = new ListNode(0); // not part of the answer; dummy.next will be the head
ListNode tail = dummy; // last node in the merged list so far
while (a != null && b != null) { // both still have a candidate
if (a.val <= b.val) { // equal: take a (stable, either side is fine)
tail.next = a;
a = a.next;
} else {
tail.next = b;
b = b.next;
}
tail = tail.next; // the node we just attached is the new end
}
tail.next = (a != null) ? a : b; // one list is empty — splice the other
return dummy.next;
}
Interview — type this
ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null) return b;
if (b == null) return a;
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (a != null && b != null) {
if (Integer.compare(a.val, b.val) <= 0) { // compare nodes, not a-b (overflow-safe habit)
tail.next = a;
a = a.next;
} else {
tail.next = b;
b = b.next;
}
tail = tail.next;
}
tail.next = (a != null) ? a : b;
return dummy.next;
}
Example 1 — simple
A = 1 → 3, B = 2.
| a | b | Compare | Attach | dummy.next chain |
|---|---|---|---|---|
| 1 | 2 | 1 ≤ 2 | 1 from A | 1 |
| 3 | 2 | 3 > 2 | 2 from B | 1 → 2 |
| 3 | null | B empty | splice A | 1 → 2 → 3 |
Answer: 1 → 2 → 3.
Example 2 — every step
A = 1 → 4 → 5, B = 1 → 3 → 4. This is two of the three lists from the merge-k notebook family. Equal values: this code takes A.
| Step | a.val | b.val | Integer.compare | tail.next gets | Merged so far (dummy.next) |
|---|---|---|---|---|---|
| 1 | 1 | 1 | ≤ 0, take A | A’s 1 | 1A |
| 2 | 4 | 1 | > 0, take B | B’s 1 | 1A → 1B |
| 3 | 4 | 3 | > 0, take B | B’s 3 | 1 → 1 → 3 |
| 4 | 4 | 4 | ≤ 0, take A | A’s 4 | 1 → 1 → 3 → 4A |
| 5 | 5 | 4 | > 0, take B | B’s 4 | 1 → 1 → 3 → 4 → 4B |
| 6 | 5 | null | splice A | A’s 5 | 1 → 1 → 3 → 4 → 4 → 5 |
Answer: 1 → 1 → 3 → 4 → 4 → 5. Merge k does this among k heads with a min-heap instead of two pointers.
PriorityQueue of heads with Integer.compare on val replaces the two-pointer choice.
- Moving
tailbefore attaching — you write into the wrong node. - Forgetting to splice the leftover list. Last nodes vanish.
- Comparing with
a.val - b.valon huge ints. UseInteger.comparein Live Code.
| Problem | Pattern | Links |
|---|---|---|
| Merge k sorted lists | Min-heap of k heads; two-list merge is the brute | Java in ch.05 · cheat card · clue |
| Max-sum switching lists | Two sorted lists, jump at common values — unnamed LC 962 | Java in ch.05 · cheat card · R4 coding · clue |
Middle of the list — fast / slow
Recognize it from — “return the middle node,” split a list for merge-sort or for a balanced BST, or detect a cycle (fast/slow is the same idea; cycle is a different stop condition).
What it actually does — slow moves one node, fast moves two. When fast can no longer take two steps, slow is in the middle. You do not count n first. On an even-length list this starting setup (slow = fast = head, loop while fast != null && fast.next != null) lands on the second middle — the start of the right half. Sorted-list-to-BST uses that mid as the root (or counts n and walks n/2; both are fine if you say which).
Simple — read this first
ListNode middle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) { // fast still has a two-step
slow = slow.next; // +1
fast = fast.next.next; // +2
}
return slow; // even: second middle; odd: the unique middle
}
Interview — type this
ListNode middleNode(ListNode head) {
if (head == null || head.next == null) return head; // 0/1 node
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow; // 4 nodes → node 3 (second middle). First middle: start fast = head.next
}
Example 1 — simple
Five nodes: 1 → 2 → 3 → 4 → 5. Unique middle should be 3.
| After | slow | fast | fast.next | Loop again? |
|---|---|---|---|---|
| start | 1 | 1 | 2 | yes (fast and fast.next live) |
| step 1 | 2 | 3 | 4 | yes |
| step 2 | 3 | 5 | null | no |
Answer: node 3.
Example 2 — every step
Even length: 1 → 2 → 3 → 4. Two middles: 2 (first) and 3 (second). This code returns the second middle.
| After | slow | fast | fast.next | fast.next.next | Action |
|---|---|---|---|---|---|
| start | 1 | 1 | 2 | 3 | enter loop |
| step 1 | 2 | 3 | 4 | null | fast.next lives → enter again |
| step 2 | 3 | null | — | — | fast is null → stop |
Answer: node 3 (second middle). If they want the first middle, start fast = head.next or stop when fast.next.next == null. Say which before you type.
- Loop condition
fast.next != nullwithoutfast != null— NPE when fast lands on null (even length, second step). - Not saying which middle you return on even n. Sorted LL → BST cares.
- Advancing slow twice. Then you overshoot.
| Problem | Pattern | Links |
|---|---|---|
| Sorted LL → height-balanced BST; first missing positive | Mid as BST root (count n or slow-fast) | cheat card · clue · clue (FMP) |
| Single element in sorted array; remove consecutive LL nodes summing to 0 | Dummy + prefix map on the list (not mid) | cheat card |
See also on this topic
Merge two is the inner loop of merge k. Reverse is the inner loop of reverse-k. Fast/slow mid is how a sorted list becomes a balanced BST. Max-sum switching lists is a two-list walk with a dummy — different rule (pick the better segment between shared nodes), same pointer hygiene. None of these is a Job 10454435 forecast.
| Problem | Pattern | Links |
|---|---|---|
| Merge k sorted lists | Heap of heads | Java in ch.05 · cheat card |
| Max-sum switching lists | Do not stamp LC 962 | Java in ch.05 · cheat card · R4 coding |
| Reverse k-group | Dummy + leftover policy | R4 coding |