LeetCode #25 — Hard

Reverse Nodes
in k-Group

A complete visual walkthrough of iterative k-group linked list reversal — from brute force to optimal O(1) space.

Solve on LeetCode
The Problem

Problem Statement

Given the head of a linked list, reverse the nodes of the list k at a time, and return the modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as it is.

You may not alter the values in the list's nodes, only nodes themselves may be changed.

Example 1:

Input: head = [1,2,3,4,5], k = 2
Output: [2,1,4,3,5]

Example 2:

Input: head = [1,2,3,4,5], k = 3
Output: [3,2,1,4,5]

Constraints:

  • The number of nodes in the list is n.
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

Follow-up: Can you solve the problem in O(1) extra memory space?

Patterns Used

Roadmap

  1. Brute Force — Store and Rebuild
  2. The Core Insight — Process k at a Time
  3. Algorithm Walkthrough
  4. Edge Cases
  5. Full Annotated Java Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Store and Rebuild

The simplest approach: copy all node values into an array, reverse every consecutive group of k values in the array, then rebuild the linked list from the modified array.

Example: [1, 2, 3, 4, 5] with k = 3

original
1
2
3
4
5
↓ reverse groups of 3 in array ↓
reversed
3
2
1
4
5

First 3 values are reversed. The last 2 values (less than k) remain in their original order. Then rebuild a new linked list from this array.

This works but uses O(n) extra space for the array. The problem specifically asks: "Can you solve the problem in O(1) extra memory space?" We need to reverse the links in-place.

💡

The real challenge: Reversing a section of a linked list is straightforward. The tricky part is reconnecting each reversed group to the previous group and the next group without losing any references.

Step 02

The Core Insight — Process k at a Time

We process the list in groups of k nodes. For each group, we perform four sub-steps:

The Four Sub-Steps

1. Check if k nodes remain
Walk forward k steps from the current position. If we hit null before k steps, stop — the remaining nodes stay as-is.
2. Reverse the k nodes
Use the standard linked list reversal technique: for each node, point its next to the previous node.
3. Reconnect the reversed segment
The node before the group now points to the new first node (formerly the k-th node). The old first node (now last in the group) points to the rest of the list.
4. Advance to the next group
Move groupPrev to the end of the just-reversed group (the old first node). Repeat.

Why a dummy head? The first group has no "previous node." A dummy node before the head gives us a uniform groupPrev pointer for every group, including the first one. This eliminates special-case handling.

Before and After: One Group Reversal (k = 3)

before
prev
1
2
3
next
after
prev
3
2
1
next

Notice how prev now points to node 3 (the new head of the group), and node 1 (the old head, now the tail) points to next (the start of the unreversed portion). The groupPrev pointer then advances to node 1.

Step 03

Algorithm Walkthrough

Let's trace through [1, 2, 3, 4, 5] with k = 3 step by step.

Initial State

We create a dummy node pointing to the head. groupPrev starts at the dummy.

dummy
1
2
3
4
5
→ null
groupPrev = dummy

Iteration 1 — Check: Do 3 nodes remain?

Starting from groupPrev (dummy), walk 3 steps: dummy→1→2→3. We reach node 3, so kth = 3. Three nodes remain — proceed with reversal.

dummy
1
2
3
|
4
5
→ null
group to reverse: [1, 2, 3]

Iteration 1 — Reverse [1, 2, 3]

Reverse the pointers within the group. prev starts as groupNext (node 4), so node 1 will point to node 4 after reversal.

step 1: curr=1, point 1→4
3
2
1
4
step 2: curr=2, point 2→1
3
2
1
4
step 3: curr=3, point 3→2
3
2
1
4

Iteration 1 — Reconnect

Set groupPrev.next = kth (dummy→3) and advance groupPrev to the old first node (node 1, now at the tail of the reversed group).

dummy
3
2
1
4
5
→ null
groupPrev = node 1

Iteration 2 — Check: Do 3 nodes remain?

Starting from groupPrev (node 1), walk 3 steps: 1→4→5→null. We hit null before completing 3 steps, so kth = null. Only 2 nodes remain — not enough for a full group.

dummy
3
2
1
4
5
→ null
kth = null → break!

Final Result

3
2
1
4
5
→ null
Output: [3, 2, 1, 4, 5]
Step 04

Edge Cases

These are the scenarios you should keep in mind when implementing or testing:

  • k = 1 Every group has exactly 1 node. Reversing a single node is a no-op — the list is returned unchanged. Our algorithm handles this naturally since reversing 1 element does nothing.
  • k = n k equals the list length. The entire list is one group and gets fully reversed. Example: [1,2,3] with k=3 yields [3,2,1].
  • k > n k is greater than the list length. The getKth check fails immediately — no reversal happens. The list is returned as-is.
  • single node A list with only one node. For any k ≥ 1, either k=1 (no-op) or k>1 (not enough nodes). The result is always the same single node.
📋

Constraint reminder: The problem guarantees 1 ≤ k ≤ n, so k > n won't appear in LeetCode test cases. But a robust implementation handles it anyway — and ours does, for free.

Step 05

Full Annotated Java Code

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int val, ListNode next) {
 *         this.val = val; this.next = next;
 *     }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {

        // Dummy node simplifies head-of-list edge case
        ListNode dummy = new ListNode(0, head);
        ListNode groupPrev = dummy;

        while (true) {
            // 1. Find the k-th node from groupPrev
            ListNode kth = getKth(groupPrev, k);
            if (kth == null) break;  // fewer than k nodes left

            // 2. Save the node after this group
            ListNode groupNext = kth.next;

            // 3. Reverse the k nodes in this group
            //    prev starts at groupNext so the last reversed
            //    node points to the rest of the list
            ListNode prev = groupNext;
            ListNode curr = groupPrev.next;

            while (curr != groupNext) {
                ListNode next = curr.next;  // save next
                curr.next = prev;           // reverse pointer
                prev = curr;                // advance prev
                curr = next;                // advance curr
            }

            // 4. Reconnect: the old first node is now the tail
            ListNode tmp = groupPrev.next;  // old first (now tail)
            groupPrev.next = kth;           // point to new head (kth)
            groupPrev = tmp;                // advance to tail for next iteration
        }

        return dummy.next;
    }

    // Walk k steps from node; return null if fewer than k nodes remain
    private ListNode getKth(ListNode node, int k) {
        while (node != null && k > 0) {
            node = node.next;
            k--;
        }
        return node;
    }
}
💡

Key detail in the reversal loop: Notice prev starts as groupNext, not null. This is what seamlessly connects the tail of the reversed group to the rest of the list. Without this, node 1 would point to null instead of node 4, breaking the chain.

Step 06

Interactive Demo

Enter a list and a value for k, then step through the algorithm to see each group identified and reversed in real time.

⚙ k-Group Reversal Visualizer



Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

Each node is visited at most twice: once during the getKth length check and once during the reversal. Across all n/k groups, the getKth calls walk a combined n + k steps (including the final failing check), and the reversals perform exactly n pointer swaps. Total: O(n). We use only a constant number of pointer variables -- no arrays, stacks, or recursion -- for O(1) space.

Approach Breakdown

BRUTE FORCE
O(n) time
O(n) space

Copies all n node values into an array, reverses every k-length chunk with simple index swapping, then rebuilds a new linked list. The array allocation and the rebuilt list each use O(n) extra memory.

IN-PLACE
O(n) time
O(1) space

Each node is visited at most twice: once during the getKth length check and once during pointer reversal. Across all n/k groups this sums to O(n). Only a constant number of pointer variables (groupPrev, curr, prev, kth) are needed -- no arrays or recursion.

🎯

Even though we reverse k-length segments, each node participates in exactly one reversal, so total work is O(n). The brute force approach (copy to array, reverse chunks, rebuild list) also runs in O(n) time but requires O(n) extra space. Our in-place solution matches the time complexity with O(1) space -- exactly what the problem asks for.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Losing head/tail while rewiring

Wrong move: Pointer updates overwrite references before they are saved.

Usually fails on: List becomes disconnected mid-operation.

Fix: Store next pointers first and use a dummy head for safer joins.