On this page
Description
Group reversal reverses a linked list in pieces: k nodes at a time (Reverse Nodes in k-Group), or only the nodes between two positions (Reverse Linked List II). Both reuse the plain reversal loop. The new work is bookkeeping at the edges of each piece. You check that a group is complete before touching it, and you stitch each reversed piece back to the nodes before and after it.
We'll go from simple to more technical:
- The pattern: reverse k at a time, only when k nodes remain.
- A second flavor: reverse one subrange by repeatedly moving nodes to its front.
- Where batch-at-a-time and edit-in-place processing show up.
- The stitching bugs, and recursion depth on long lists.
The approach
Reversing a whole list is one loop. In groups of k, a short last group is usually left as it is. So first count ahead: are there k nodes? If not, return the rest unchanged. If so, reverse exactly those k nodes and connect the group's new tail to the rest of the list, which the recursion has already handled.
Reversing 4 -> 9 -> 2 -> 7 -> 5 -> 1 -> 8 in groups of 3:
| Group | Nodes | Full group? | After |
|---|---|---|---|
| 1 | 4 9 2 | yes | 2 9 4 |
| 2 | 7 5 1 | yes | 1 5 7 |
| 3 | 8 | no, only 1 node | 8, unchanged |
The result is 2 -> 9 -> 4 -> 1 -> 5 -> 7 -> 8. Note the stitch: 4, which was the first group's head, is now its tail and must point to 1, the second group's new head.
Java
ListNode reverseKGroup(ListNode head, int k) {
ListNode node = head;
int count = 0;
while (node != null && count < k) { node = node.next; count++; }
if (count < k) return head; // fewer than k nodes left - leave unchanged
ListNode newHead = reverseKGroup(node, k); // recursively handle everything after this group first
ListNode curr = head;
ListNode prev = newHead;
for (int i = 0; i < k; i++) { // reverse exactly this group of k nodes
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev; // the new head of this (now-reversed) group
}
Python
def reverse_k_group(head: ListNode | None, k: int) -> ListNode | None:
node = head
count = 0
while node is not None and count < k:
node = node.next
count += 1
if count < k:
return head # fewer than k nodes left - leave unchanged
new_head = reverse_k_group(node, k) # recursively handle everything after this group first
curr = head
prev = new_head
for _ in range(k): # reverse exactly this group of k nodes
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev # the new head of this (now-reversed) group
Counting first is what keeps the short last group simple: it's an early return, not an undo. The recursion handles the rest of the list before this group is reversed. So prev starts out pointing at the already-finished remainder, and the reversal links the group straight into it.
A second flavor: reversing one subrange
Reversing only positions left to right needs a different move. Keep an anchor, prev, on the node just before the range. Keep curr on the range's first node; it will end up as the range's tail, so it never moves. Then, again and again, take the node right after curr and move it to the front of the range, right after the anchor.
Reversing positions 2 to 4 of 4 -> 9 -> 2 -> 7 -> 5 (the anchor is 4, curr is 9):
| Step | Node moved to the front | List |
|---|---|---|
| Start | 4 -> 9 -> 2 -> 7 -> 5 | |
| 1 | 2 | 4 -> 2 -> 9 -> 7 -> 5 |
| 2 | 7 | 4 -> 7 -> 2 -> 9 -> 5 |
Java
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;
for (int i = 0; i < left - 1; i++) prev = prev.next; // prev: the anchor, fixed just before the range
ListNode curr = prev.next; // curr: fixed at the range's ORIGINAL start - it becomes the range's tail
for (int i = 0; i < right - left; i++) {
ListNode moved = curr.next; // pull the node right after curr...
curr.next = moved.next;
moved.next = prev.next; // ...and insert it right after the anchor
prev.next = moved;
}
return dummy.next;
Python
def reverse_between(head: ListNode | None, left: int, right: int) -> ListNode | None:
dummy = ListNode(0, head)
prev = dummy
for _ in range(left - 1):
prev = prev.next # prev: the anchor, fixed just before the range
curr = prev.next # curr: fixed at the range's ORIGINAL start - it becomes the range's tail
for _ in range(right - left):
moved = curr.next # pull the node right after curr...
curr.next = moved.next
moved.next = prev.next # ...and insert it right after the anchor
prev.next = moved
return dummy.next
Where you'll see it
Processing a stream in fixed-size batches works the same way: measure a full batch, act on it as a whole, and hold back a partial last batch. A buffer that flushes only full pages does exactly this. The subrange version is like editing one known section of a document in place.
Which tool for which job?
| You need | Use |
|---|---|
| Reverse every k nodes, leave a short tail | check k nodes exist, then reverse that group |
| The same in O(1) extra space | iterative, keeping the previous group's tail |
| Reverse positions left..right | anchor before the range, move each next node to the front |
| Swap every pair | k-group with k = 2 |
| Reverse the short tail too | drop the "full group" check for the last group |
| Edits that may include the head | a dummy node before it |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| Reversing before counting k nodes | a short tail gets reversed | count first, then reverse |
| Not linking the previous group to the new head | groups fall off the list | groupPrev.next = kth, then move groupPrev |
Advancing curr in subrange reversal | the wrong nodes move | curr stays the range's first node |
| No dummy when the range starts at 1 | special case for the head | anchor at a dummy |
| Recursing once per group on long lists | stack overflow | iterate |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. Why count k nodes before reversing anything?
Show answer
If you reverse first and then find fewer than k nodes, you have to undo the reversal, which is easy to get wrong. Counting first makes "leave a short last group alone" a simple early return.
2. After a group is reversed, why must its old head be linked to the next group?
Show answer
Reversing flips every link inside the group, so the old head is now the tail. Without a link forward, it points at nothing useful, and the groups become separate fragments instead of one list.
3. In subrange reversal, why does curr never move?
Show answer
curr is the range's first node, and it ends up as the range's tail. Each step takes the node right after curr and moves it to the front. That only works if curr stays put. Advance it and you start moving the wrong nodes.
Key takeaways
- Count k nodes before reversing a group, so a short last group is a simple early return.
- A reversed group's old head is its new tail. Link it to the next group's new head.
- To reverse a subrange, keep an anchor before it and keep moving the node after the range's first node to the front.
- In that loop, the range's first node stays fixed; it becomes the tail.
- The two techniques look alike but rewire differently. Copying one's loop into the other gives subtly wrong lists.