2Brute2Best
🔥0

Linked List — Reversal Pattern: Algorithmic Intuition, Sub-patterns & C++ Guide

Reverse links in place with three pointers (prev, curr, next). Most hard list problems are "reverse a piece, then reconnect".

4 sub-patterns · 16 questions

Remember this · Linked List — Reversal
Trigger
Reverse whole / part / every k nodes, reorder, palindrome check, numbers stored as lists.
Think
Which piece do I reverse, and which nodes must I remember to reconnect afterwards?
Key question
Before reversing, who is the node just before the piece and who is just after?
A

What is this pattern?

A linked list is a chain of arrows. To reverse part of it you flip the arrows, one node at a time, and the hard part is never losing the rest of the chain. Linked List — Reversal is the family of problems where you reverse a whole list, a range, or blocks of k nodes, and then reconnect the pieces.

The core move is always the same: save next before you flip it. Flipping cur->next throws away the only link to the rest of the list. Every variant below is that move plus a question about where the reversed piece starts and ends.

A line of people holding hands, each facing forward. To reverse the line you turn each person around, one at a time, and you have to ask the next person to step forward before you let go of their hand. Reversing a range is the same, except the people at each end of the range must be reconnected to the rest of the line afterwards.

B

See it move

Two tracks: the reversed part (grows at the front) and the part still to do. The blue cell is the node whose arrow was just flipped. The arrow points back to the reversed part, so the rest of the list is never lost.

Full reversal: flip one arrow per steplist = [1, 2, 3, 4, 5]step 1/6
reversed
0
null
▲prev
still to do
0
1
2
3
4
5
1
2
3
4
5
null
▲cur
Full reversal
prev
null
cur
node 0 (val 1)
start: prev = null, cur = head

Nothing is reversed yet. cur is node 0 (val 1). Each step saves cur->next, flips cur->next to point at prev, then moves prev and cur one node forward.

The blue band is positions 2 to 5. The first node of the range (the pivot) never moves; each orange node is pulled to the front of the range. The node before the range is only used to reconnect.

Sublist reversal: head insertionlist = [1, 2, 3, 4, 5, 6], left = 2, right = 5step 1/4
list
0
1
2
3
4
5
1
2
3
4
5
6
▲pivot
▲before
Sublist reversal
left
2
right
5
before
node 0 (val 1)
pivot
2
reverse positions 2..5; the blue band is the range

The node before the range is val 1. The first node of the range (val 2) stays put; the nodes after it jump to the front of the range one at a time.

C

How do I recognise it?

  • "Reverse the list", "reverse between position left and right", or "reverse every k nodes".

  • Reorder the list: put the first half and the reversed second half back together (L0 → Ln → L1 → Ln-1 …).

  • Numbers stored as lists, most significant digit first (add two numbers, double a number): reverse, work from the units digit, then reverse back.

  • A removal rule that looks at the nodes to the right of each node ("remove a node if something bigger is after it"): reverse, process from the left, reverse back.

  • Rotate or split a list into pieces and join them in a new order.

  • The brute force copies the values into an array, reverses the array, and writes the values back into new nodes.

Looks like Linked List — Reversal, but isn't

Find the middle, detect a cycle, or find the n-th node from the end
→ Linked List — Fast & Slow

Fast & Slow only reads positions. Reversal rewires next pointers. Palindrome and reorder use both: fast & slow find the middle, then reversal flips the second half.

Reverse an array or a string
→ Two Pointers (opposite ends)

An array can swap values at both ends with two indices. A singly linked list can't walk backwards, so each arrow must be flipped while the walk goes forward, with prev remembering where it came from.

Merge two sorted lists
→ Two Pointers or K-way Merge

Merging only takes the smaller head each time. Nothing reverses. Reorder uses a merge-like weave, but only after one half has been reversed.

D

How would I discover it from scratch?

Don't memorise “use linked list — reversal”. Ask these questions in order — the pattern falls out.

Copy all values into an array, reverse the array, and write the values back. That is O(N) extra space and it rebuilds nodes. Pointer reversal does the same job by flipping next in place with O(1) extra space.

E

Sub-patterns

Same core idea, different rule for “valid”. Learn them in this order.

F

Don't mix these up

Full reversal vs Sublist reversal

Full reversal starts at the head and the old tail becomes the new head. Sublist reversal needs a node before the range, which is why a dummy node is used, and the nodes outside the range must not move.

Reversing one block vs Joining the blocks together

After a block is reversed, its old first node is now the last node of that block, and it must point at the next block's head. The block itself reverses correctly; the link between blocks is what people forget.

Reorder by reversal vs Reorder by splitting

Reorder List needs the first half cut from the second. Without slow->next = nullptr the two halves share a chain and the weave creates a cycle.

G

Code templates (C++)

Full reversal

Save nxt before cur->next = prev. The loop ends when cur is null, and prev is the new head.

reverse-template.cpp
// Full reversal: walk once, pointing each node back to the previous one.
// Returns the new head (the old tail).
ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;                  // the reversed part so far (empty at first)
    ListNode* cur = head;                      // the next node to flip
    while (cur != nullptr) {
        ListNode* nxt = cur->next;             // 1. save the rest of the list BEFORE losing it
        cur->next = prev;                      // 2. flip this arrow
        prev = cur;                            // 3. the reversed part now starts at cur
        cur = nxt;                             // 4. move on to the saved node
    }
    return prev;                               // cur ran off the end, so prev is the old tail
}

Sublist reversal (positions left to right)

Use a dummy node before the head. Walk to the node before left, then move the node after the pivot to the front of the range right - left times. Return dummy.next, because left = 1 changes the head.

sublist-template.cpp
// Sublist reversal: reverse nodes at positions left..right (1-based), keep the rest.
// Head insertion: walk to the node before the range, then pull the node after
// the pivot to the front of the range, one at a time.
ListNode* reverseBetween(ListNode* head, int left, int right) {
    ListNode dummy(0, head);                   // a fixed node before head, so left = 1 is no special case
    ListNode* before = &dummy;
    for (int i = 1; i < left; i++) before = before->next;   // before = the node just before the range

    ListNode* pivot = before->next;            // the first node of the range; it stays put
    for (int i = left; i < right; i++) {       // right - left flips
        ListNode* moved = pivot->next;         // 1. the node that jumps to the front
        pivot->next = moved->next;             // 2. close the gap it leaves behind
        moved->next = before->next;            // 3. it points at the current front of the range
        before->next = moved;                  // 4. it becomes the new front
    }
    return dummy.next;                         // the dummy's next is the new head
}

Reverse in groups of k

Count k nodes ahead before touching anything. If the count falls short, stop. Otherwise reverse the block and join it to groupPrev. The old first node becomes the next groupPrev.

kgroup-template.cpp
// Groups of k: reverse each full block of k nodes; a short last block stays as it is.
// Count k nodes ahead first. If the block is full, reverse it, then join it to the
// previous block's tail (the old first node of this block is now its last).
ListNode* reverseKGroup(ListNode* head, int k) {
    ListNode dummy(0, head);
    ListNode* groupPrev = &dummy;              // the node just before the current block

    while (true) {
        // 1. Is there a full block of k nodes starting after groupPrev?
        ListNode* kth = groupPrev;
        for (int i = 0; i < k && kth != nullptr; i++) kth = kth->next;
        if (kth == nullptr) break;             // fewer than k left: leave the rest alone

        ListNode* groupNext = kth->next;       // the node after this block (saved before relinking)
        ListNode* first = groupPrev->next;     // the old first node; it becomes the block's last

        // 2. Reverse the block with head insertion (same as the sublist template).
        ListNode* prev = groupNext;            // the reversed block must end at groupNext
        ListNode* cur = first;
        while (cur != groupNext) {
            ListNode* nxt = cur->next;
            cur->next = prev;
            prev = cur;
            cur = nxt;
        }

        // 3. Join: the previous block now ends at the new first node (kth).
        groupPrev->next = kth;
        groupPrev = first;                     // the old first is this block's tail for the next round
    }
    return dummy.next;
}

Split, reverse, and weave (reorder list)

Cut the first half from the second (slow->next = nullptr) before reversing. Without that cut the two halves still share a chain and the weave makes a cycle.

reorder-template.cpp
// Split, reverse, and re-weave: L0 → Ln → L1 → Ln-1 → L2 → …
// Three steps. Find the end of the first half, reverse the second half,
// then interleave the two halves one node at a time.
void reorderList(ListNode* head) {
    if (head == nullptr || head->next == nullptr) return;

    // 1. Find the end of the first half (fast and slow). slow stops on the first middle.
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast->next != nullptr && fast->next->next != nullptr) {
        slow = slow->next;
        fast = fast->next->next;
    }

    // 2. Cut the list in two, then reverse the second half.
    ListNode* second = slow->next;
    slow->next = nullptr;                      // cut: without this the first half still runs into the second
    ListNode* prev = nullptr;
    while (second != nullptr) {
        ListNode* nxt = second->next;
        second->next = prev;
        prev = second;
        second = nxt;
    }
    second = prev;                             // head of the reversed second half (the old tail)

    // 3. Weave: take one node from the first half, then one from the second, until one runs out.
    ListNode* first = head;
    while (second != nullptr) {
        ListNode* f = first->next;             // save both continuations before relinking
        ListNode* s = second->next;
        first->next = second;                  // L0 → Ln
        second->next = f;                      // Ln → L1
        first = f;
        second = s;
    }
}