Chapter 11 · Linked lists

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 →

Reverse — three pointersIterative while loop first. Recursive reverse is the interview block.
Merge two sorted listsDummy head so the first attach has no special case.
Fast / slow middleOdd length is unique. Even length: this code returns the second middle.
Shared node Every snippet below uses a singly linked 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.

start: prev=null cur=1 → 2 → 3 want: 3 → 2 → 1
Dry-run — 1 → 2 → 3
After iternxtprevcurList you can see
before loop—null11 → 2 → 3
12121 | 2 → 3  (1.next is null)
23232 → 1 | 3
3null3null3 → 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.

1 → 2 → 3 → 4 (head always names node 1) after: 4 → 3 → 2 → 1
Every iteration — nxt / prev / cur / what head.next is
Iternxt savedcur.next = prevthen prevthen curhead (node 1) .nextPicture
start——null121 → 2 → 3 → 4
121.next = null12null1  |  2 → 3 → 4
232.next = 123null2 → 1  |  3 → 4
343.next = 234null3 → 2 → 1  |  4
4null4.next = 34nullnull4 → 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.

Time O(n) Iterative space O(1) Recursive space O(n) call stack
Why three pointers A stack of nodes also reverses, but it is O(n) extra and hides the pointer rewrite they want to see. Recursion is the same stack, just implicit. Live Code: type iterative first; recursive is the follow-up if they ask “can you do it without prev.”
Common mistakes
  • Forgetting to save nxt before cur.next = prev — the rest of the list is gone.
  • Returning the original head after the loop. That node is now the tail.
  • Recursive version: forgetting head.next = null. You leave a cycle 1 ⇄ 2.
ProblemPatternLinks
Reverse in groups of kDummy + reverse a window of k, leftover policy asked out loudR4 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: 1 → 3 B: 2 dummy → (empty), tail = dummy
Dummy walk — 1 → 3 and 2
abCompareAttachdummy.next chain
121 ≤ 21 from A1
323 > 22 from B1 → 2
3nullB emptysplice A1 → 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.

A: 1 → 4 → 5 B: 1 → 3 → 4
Every dummy walk — 1→4→5 and 1→3→4
Stepa.valb.valInteger.comparetail.next getsMerged so far (dummy.next)
111≤ 0, take AA’s 11A
241> 0, take BB’s 11A → 1B
343> 0, take BB’s 31 → 1 → 3
444≤ 0, take AA’s 41 → 1 → 3 → 4A
554> 0, take BB’s 41 → 1 → 3 → 4 → 4B
65nullsplice AA’s 51 → 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.

Time O(n + m) Space O(1) extra
Why a dummy Without a dummy you special-case “which list starts the answer.” Dummy makes the first attach the same as the rest. You still mutate the existing nodes — you do not allocate a parallel list of values. For k lists, a PriorityQueue of heads with Integer.compare on val replaces the two-pointer choice.
Common mistakes
  • Moving tail before attaching — you write into the wrong node.
  • Forgetting to splice the leftover list. Last nodes vanish.
  • Comparing with a.val - b.val on huge ints. Use Integer.compare in Live Code.
ProblemPatternLinks
Merge k sorted listsMin-heap of k heads; two-list merge is the bruteJava in ch.05 · cheat card · clue
Max-sum switching listsTwo sorted lists, jump at common values — unnamed LC 962Java 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.

1 → 2 → 3 → 4 → 5 ↑ middle
Odd length — 5 nodes
Afterslowfastfast.nextLoop again?
start112yes (fast and fast.next live)
step 1234yes
step 235nullno

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.

1 → 2 → 3 → 4 ↑ first ↑ second ← we return this
Even length — 4 nodes, every pointer move
Afterslowfastfast.nextfast.next.nextAction
start1123enter loop
step 1234nullfast.next lives → enter again
step 23null——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.

Time O(n) Space O(1)
Why two pointers, not a count Counting n then walking n/2 is also O(n). Fast/slow does it in one pass without storing n, and the same skeleton becomes cycle detection (if fast meets slow) or “split for merge-sort / BST.”
Common mistakes
  • Loop condition fast.next != null without fast != 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.
ProblemPatternLinks
Sorted LL → height-balanced BST; first missing positiveMid as BST root (count n or slow-fast)cheat card · clue · clue (FMP)
Single element in sorted array; remove consecutive LL nodes summing to 0Dummy + 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.

ProblemPatternLinks
Merge k sorted listsHeap of headsJava in ch.05 · cheat card
Max-sum switching listsDo not stamp LC 962Java in ch.05 · cheat card · R4 coding
Reverse k-groupDummy + leftover policyR4 coding

← Prefix Sum · Stacks & Queues →