跪拜 Guibai
← Back to the summary

Every Linked List Operation Is Just `cur.next = ...` — Here's How to Derive, Not Memorize

Ten Linked List Problems Memorized to Death? One Line cur.next = cur.next.next Solves Them All


Opening: Four Operations, You Memorized Them Four Times

When you grind linked list problems, is this what happens:

You treat each operation as an independent knowledge point, and your brain becomes a mess after grinding. When doing LeetCode 203 (Remove Linked List Elements), you forget whether to advance after deleting; when doing 206 (Reverse Linked List), you mix up the movement order of pre and cur.

This article shifts perspective — don't treat linked list operations as four independent techniques, but as four variants of "changing the next pointer." Once you understand this, you can derive solutions for linked list problems you've never seen before.


Core Concept: Linked List = A Bunch of Nodes, Each Pointing to the Next

A linked list is like "beads on a string." Each bead (node) holds an address telling you where the next bead is. The essence of all operations is modifying the address inside a bead — that is, changing next.

Understand it through a treasure hunt:

You get note A, which says "go to the library"
At the library, you find note B, which says "go to the cafeteria"
At the cafeteria, you find note C, which says "the treasure is here"

Note A          Note B           Note C
[to library] → [to cafeteria] → [treasure here] → null
  next           next             next
Real-world Action Linked List Operation Essence
Follow the notes one by one Traversal Follow next forward
Change note A to "go to cafeteria" (skip B) Deletion Make the previous node's next skip the target
Insert a new note between A and B Insertion New node points to the next node, previous node points to new node
Rewrite all note addresses in reverse Reversal Change each node's next to point to the previous one

One diagram to understand all operations:

graph LR
    A["Node A"] -->|"next"| B["Node B"]
    B -->|"next"| C["Node C"]
    C -->|"next"| D["null"]

    A2["Node A"] -.->|"Delete: skip B"| C2["Node C"]
    B2["New Node X"] -.->|"Insert: between A and B"| B3["Node B"]

Above: Solid arrows are next pointers. Deletion = change A's next from "point to B" to "point to C". Insertion = new node X's next points to B, A's next changes to point to X.


Four Major Operations: All Solved by One Line cur.next = ...

A linked list node in JavaScript looks like this:

function ListNode(val, next) {
    this.val = (val === undefined ? 0 : val);
    this.next = (next === undefined ? null : next);
}

// Create linked list 1 → 2 → 3
let head = new ListNode(1, new ListNode(2, new ListNode(3)));

Operation One: Traversal — The Simplest Change of next

Traversal essentially means "move the current pointer to the next node":

let cur = head;
while (cur) {              // cur becomes null means we reached the end
    console.log(cur.val);
    cur = cur.next;        // 🔑 What "changes" is the temporary variable cur, not the list itself
}

cur = cur.next means "I shift my gaze from the current node to the next one." This differs from deletion/insertion — traversal doesn't change the list structure, only where you "look."


Operation Two: Deleting a Node — Skip It

LeetCode 203. Remove Linked List Elements

To delete a node with value val, the core is making the predecessor node's next skip the target node directly:

Before deletion:  cur → node to delete → nodes behind
After deletion:   cur → nodes behind (the deleted node has no references, gets garbage collected)
function removeElements(head, val) {
    // 🔑 dummy virtual head node: gives head a "predecessor" too, unifying processing logic
    let dummy = new ListNode(0, head);
    let cur = dummy;

    while (cur.next) {
        if (cur.next.val === val) {
            cur.next = cur.next.next;  // 🔑 The only key line: skip the node to delete
            // ⚠️ Don't advance cur here! Because cur.next already points to the new node,
            //     the next while iteration will check exactly this new node
        } else {
            cur = cur.next;            // Not deleting: advance normally
        }
    }
    return dummy.next;  // 🔑 Return dummy.next, not head — head might have been deleted
}

⚠️ Most common bug: After deleting a node, doing cur = cur.next, which skips checking the new cur.next (the original next-next). Don't advance after deletion — this is the highest-frequency mistake in problem 203.


Operation Three: Inserting a Node — Connect First, Then Break

Insert a new node after cur. The order is an iron rule: must connect the new one first, then break the old one.

① newNode.next = cur.next    First make the new node point to cur's next
② cur.next = newNode         Then make cur point to the new node
function insertAfter(cur, val) {
    let newNode = new ListNode(val);
    newNode.next = cur.next;   // 1. New node first connects to the chain behind
    cur.next = newNode;        // 2. cur then connects to the new node
    // ⚠️ Reversing the order loses data! If you do ② first, the link between cur and the nodes behind is broken,
    //     all subsequent nodes become unfindable and get garbage collected
}
sequenceDiagram
    participant C as cur
    participant N as newNode
    participant R as rest

    Note over C,R: Before insertion: cur → rest

    N->>R: ① newNode.next = cur.next
    Note over N,R: newNode → rest (connect first)

    C->>N: ② cur.next = newNode
    Note over C,N: cur → newNode → rest (done!)

Why connect first then break? It's like moving house — first get the new key made, then cancel the old address. If you cancel the old address first, you can't go back to get your stuff.


Operation Four: Reversing a Linked List — Flip Direction One by One

LeetCode 206. Reverse Linked List

The essence of reversal: traverse each node, changing its next to point to the previous node. Requires three variables: pre (previous), cur (current), temp (temporarily store next).

Original list:  1 → 2 → 3 → null

Step 1:  null ← 1    2 → 3 → null     (1 points to null, remember 2's address)
              ↑      ↑
             pre    cur

Step 2:  null ← 1 ← 2    3 → null     (2 points to 1, remember 3's address)
                   ↑      ↑
                  pre    cur

Step 3:  null ← 1 ← 2 ← 3               (3 points to 2, cur becomes null, done)
                        ↑
                       pre → This is the new head!
function reverseList(head) {
    let pre = null;
    let cur = head;

    while (cur) {
        let temp = cur.next;    // 1. First save the next node (otherwise you lose it when the chain breaks)
        cur.next = pre;         // 2. 🔑 Reversal: current node points to the previous one
        pre = cur;              // 3. pre advances
        cur = temp;             // 4. cur advances (using the previously saved temp)
    }
    return pre;                 // pre is the new head after reversal
}

🧠 Memory aid: Save next → point back to previous → both advance together. Linked list reversal is one of the highest-frequency interview questions; drill these three steps into muscle memory.


Universal Technique: Dummy Head Node

The dummy node is a "cheat code" for linked list problems — add a fake node before the real head, so every node (including head) has a predecessor.

Original:  head → 2 → 3 → null    (head has no predecessor, requires special-case handling)
With dummy:  dummy → head → 2 → 3 → null  (every real node now has a predecessor!)
let dummy = new ListNode(0, head);  // dummy points to head
let cur = dummy;                     // start operating from dummy
// ... various operations, cur.next is "the next node to process"
return dummy.next;                   // ⚠️ Return dummy.next! head may have been deleted

When must you use dummy?

Scenario Why it's necessary
May need to delete the head node head has no predecessor; deleting it requires dummy
Unifying processing logic Don't want to write special branches for "the first node"
Returning a brand new list Use dummy to chain new nodes, finally return dummy.next

Tip: When you encounter a linked list problem, your first reaction should be "do I need a dummy?" 90% of linked list problems have unified logic after adding a dummy.


Advanced Patterns: Fast & Slow Pointers + Two Pointers

The "difficulty" in linked list problems mainly concentrates in four categories. Remember this mnemonic first:

Cycle → fast-slow chase; K-th from end → fast goes K first; Intersection → swap paths; Merge → compare and attach.

1. Detect Cycle in Linked List (LeetCode 141) — Fast & Slow Pointers Meet

Track race: Two people on a track, the fast one moves 2 steps at a time, the slow one 1 step. If there's a cycle → the fast one will always lap and catch the slow one; no cycle → the fast one reaches the finish line (null) first.

function hasCycle(head) {
    let slow = head, fast = head;
    while (fast && fast.next) {
        slow = slow.next;           // Slow: one step
        fast = fast.next.next;      // Fast: two steps
        if (slow === fast) return true;  // Caught up = has cycle
    }
    return false;                   // fast reached null = no cycle
}

2. Find Cycle Entry Point (LeetCode 142) — Floyd's Cycle Detection

After they meet, put one pointer back at the head node, and both pointers move at the same speed one step at a time. Where they meet again is the cycle entry.

function detectCycle(head) {
    let slow = head, fast = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow === fast) {           // Has cycle
            let p = head;
            while (p !== slow) {       // Same speed walk, meeting point is entry
                p = p.next;
                slow = slow.next;
            }
            return p;
        }
    }
    return null;
}

3. K-th Node from End (LeetCode 19)

Fast pointer goes K steps first, then fast and slow advance at the same speed. When fast reaches the end, slow is exactly at the K-th from the end.

4. Intersection of Two Linked Lists (LeetCode 160)

Two pointers start from the heads of the two lists respectively. When they reach the end, switch to the other list and continue. Both travel the same total distance, so they must meet at the intersection point, or reach null simultaneously.

5. Merge Two Sorted Lists (LeetCode 21)

dummy + two pointers. Two pointers point to the two lists respectively, compare values, attach the smaller one to the result chain, move the corresponding pointer. When one is empty, directly attach the rest of the other chain.

function mergeTwoLists(l1, l2) {
    let dummy = new ListNode(0);
    let cur = dummy;
    while (l1 && l2) {
        if (l1.val <= l2.val) {
            cur.next = l1;    // Attach whichever is smaller
            l1 = l1.next;     // Advance the one that was attached
        } else {
            cur.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;       // cur always points to the tail of the chain
    }
    cur.next = l1 || l2;      // Directly attach the rest
    return dummy.next;
}

Linked List vs Array: When to Choose Which

Array Linked List
Access i-th element O(1) — direct index O(n) — find from head one by one
Head insertion/deletion O(n) — shift everything O(1) — change a pointer
Memory Contiguous block, needs a whole free chunk Scattered everywhere, linked by pointers (flexible)
Traversal speed Fast (CPU cache friendly) Slow (nodes jump around)

🔑 Selection mnemonic: Need random access → Array. Frequent head insertion/deletion → Linked List.


Five Pitfalls to Avoid (Everyone Has Stepped in These)

# Pitfall Correct Approach
1 Writing while (cur) as while (cur.next) When traversing to the last node, cur.next being null is fine, but using while(cur) is safer — you just want "continue as long as cur is not null"
2 Advancing cur after deletion After deletion, cur.next already points to the new node; don't move cur, the next iteration will check it exactly
3 Breaking before connecting during insertion Always "connect the new first → then break the old": newNode.next = cur.next first, cur.next = newNode second
4 Using dummy but return head head may have been deleted or moved behind. Always return dummy.next
5 Not checking for empty list Add if (!head) return null at the entry, otherwise head.next directly causes TypeError

Conclusion: Next Time You Face a Linked List Problem, Don't Memorize Operations, Derive Them

The opening asked: why do you memorize four operations to death?

Because you treat them as independent techniques to remember, rather than understanding them as variants of "changing the next pointer."

Remember in one sentence: The essence of linked lists is changing next — traversal is changing your "gaze," deletion is skipping, insertion is connect-then-break, reversal is flipping direction one by one. All operations ultimately boil down to one line of code: someNode.next = someValue.

Next time you grind linked list problems, just do this one thing

  1. Draw a diagram of three nodes, clearly mark where each node's next points
  2. Ask yourself: which node's next to change, and change it to point to whom?
  3. Write that one line xxx.next = yyy, then fill in the steps for moving pointers before and after

Drawing + changing next. This two-step method can derive all linked list operations.

Open question: When grinding linked list problems, which operation took you a long time to understand before the lightbulb moment? Was it the rotation order of the three pointers during reversal, or the "connect first then break" during insertion? Share your epiphany moment in the comments.