Topics › Linked List › Hard Problems of LL
Reverse LL in group of given size K
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the head of a singly linked list and a positive integer k. Reverse the nodes of the list k at a time: the first k nodes are reversed as a block, then the next k, and so on. If the number of nodes left at the end is less than k, those nodes stay in their original order. Return the head of the modified list. The intended solution rearranges the nodes themselves rather than swapping values, and uses only constant extra space.
Constraints: 0 ≤ n ≤ 5000, 1 ≤ k ≤ 5000, values fit in an int.
Example 1
head = 1 -> 2 -> 3 -> 4 -> 5, k = 2
answer = 2 -> 1 -> 4 -> 3 -> 5 // the trailing block [5] has fewer than k nodes and is left alone
Example 2
head = 1 -> 2 -> 3 -> 4 -> 5, k = 3
answer = 3 -> 2 -> 1 -> 4 -> 5
Example 3
head = 1 -> 2 -> 3 -> 4 -> 5, k = 6
answer = 1 -> 2 -> 3 -> 4 -> 5 // there is no full block at all
Approach 1: Brute force, reverse values in an array
Intuition. Reversing a block of a linked list is awkward because we cannot step backwards. Reversing a block of an array is trivial. So copy the values out, reverse every full block of k entries with two indices, and write the values back into the same nodes. The node objects never move; only their payloads change.
Algorithm.
- Copy the values into a list
vals; letnbe its size. - For
start = 0, k, 2k, ...whilestart + k <= n: swapvals[i]andvals[j]forifromstartandjfromstart + k - 1moving inward. - Walk the linked list once more, writing
valsback in order. - Return
head.
static ListNode bruteForce(ListNode head, int k) {
List<Integer> vals = new ArrayList<>();
for (ListNode t = head; t != null; t = t.next) vals.add(t.val);
int n = vals.size();
for (int start = 0; start + k <= n; start += k) {
for (int i = start, j = start + k - 1; i < j; i++, j--) {
int tmp = vals.get(i);
vals.set(i, vals.get(j));
vals.set(j, tmp);
}
}
ListNode t = head;
for (int v : vals) { t.val = v; t = t.next; }
return head;
}
Complexity. Time O(n): copying, reversing (each element is swapped at most once) and writing back are all linear. Space O(n) for the copied values.
The condition start + k <= n is what leaves a short final block untouched. This approach is correct and fast, but it fails the spirit of the problem: it needs linear extra memory, and because it moves values rather than nodes, it breaks any external references that expect a particular node to carry a particular value.
Approach 2: Better, recursion
Intuition. The problem has a natural recursive structure: “reverse the first block, then solve the same problem on the rest, then connect the two”. If we first check that a full block of k nodes exists, we can recurse on the node after the block, obtaining the already-processed remainder. Then we reverse the current block with the usual three-pointer loop, but seeding prev with the remainder’s head instead of null, so the block’s old first node (which becomes its tail) points straight at the processed rest.
Algorithm.
- Walk
ksteps fromhead, counting. If fewer thanknodes exist, returnheadunchanged. - Let
nodebe the first node after the block; recursively computerestReversed = better(node, k). - Reverse the
knodes starting athead, withprevinitialised torestReversed. - Return
prev, which is now the oldk-th node and the new head of this block.
static ListNode better(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 remain: leave them unchanged
ListNode restReversed = better(node, k); // node is the first node of the next block
ListNode prev = restReversed, cur = head;
for (int i = 0; i < k; i++) { // reverse this block; its old first node ends up pointing at the rest
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev; // the old k-th node is the new first node of the block
}
Complexity. Time O(n): each node is counted once and reversed once. Space O(n / k) for the recursion stack, one frame per block; with k = 1 that is O(n) frames.
The seeding trick is the heart of this version. In an ordinary reversal prev starts as null, so the old head ends up pointing at nothing. Starting prev at restReversed means the final cur.next = prev of the first iteration wires the block’s new tail to the rest of the answer, with no separate “connect” step.
The downside is the stack. For n = 5000 and k = 1 the recursion is 5000 deep, which is usually fine in Java but not something to rely on, and interviewers will ask for the iterative version.
Approach 3: Optimal, iterative block surgery
Intuition. Keep a pointer to the tail of the previous block (initially a dummy node in front of the head). From there, walk k steps to find the k-th node of the current block. If it does not exist, we are done. Otherwise, detach the block by cutting the link after the k-th node, reverse the block with the standard loop, and splice it back: the previous block’s tail now points to the k-th node (new block head), and the block’s old first node (new block tail) points to whatever followed the block. That old first node becomes the “previous block tail” for the next iteration.
Algorithm.
- Create
dummywithdummy.next = headand setprevBlockTail = dummy. - Loop:
- Starting at
prevBlockTail, walkksteps tokth. Ifkthisnull, break. - Record
blockStart = prevBlockTail.nextandnextBlockStart = kth.next. - Set
kth.next = nullto isolate the block, thenprevBlockTail.next = reverse(blockStart). - Set
blockStart.next = nextBlockStartandprevBlockTail = blockStart.
- Starting at
- Return
dummy.next.
static ListNode optimal(ListNode head, int k) {
ListNode dummy = new ListNode(0, head);
ListNode prevBlockTail = dummy;
while (true) {
ListNode kth = prevBlockTail;
for (int i = 0; i < k && kth != null; i++) kth = kth.next; // k-th node of the current block
if (kth == null) break; // fewer than k nodes remain
ListNode blockStart = prevBlockTail.next, nextBlockStart = kth.next;
kth.next = null; // isolate the block
prevBlockTail.next = reverse(blockStart); // kth is now the first node of the block
blockStart.next = nextBlockStart; // the old first node is now the block's tail
prevBlockTail = blockStart;
}
return dummy.next;
}
static ListNode reverse(ListNode head) {
ListNode prev = null, cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
Dry run on 1 -> 2 -> 3 -> 4 -> 5, k = 2:
| prevBlockTail | kth | block | after splice |
|---|---|---|---|
| dummy | 2 | 1 -> 2 | dummy -> 2 -> 1 -> 3 -> 4 -> 5 |
| 1 | 4 | 3 -> 4 | dummy -> 2 -> 1 -> 4 -> 3 -> 5 |
| 3 | null | stop |
Result: 2 -> 1 -> 4 -> 3 -> 5.
Complexity. Time O(n): the walk to kth and the reversal together touch each node of every block twice, and the walk that fails on the final short block touches at most k - 1 nodes. Space O(1): a fixed number of pointers.
Why cut the block off before reversing? The reverse helper runs until it sees null. Without kth.next = null it would reverse the entire rest of the list. Cutting first lets us reuse the plain whole-list reversal unchanged, and nextBlockStart remembers where to reattach.
Edge cases and pitfalls
- Short final block.
[1, 2, 3, 4, 5]withk = 2must leave5in place, and withk = 3must leave4 -> 5. Any version that reverses whatever remains fails these. klarger thann. No full block exists; the list is returned as is. In Approach 3 the first walk tokthhitsnulland the loop exits immediately.k = 1. Every block is a single node; reversing it is a no-op and the list is unchanged. Approach 2 recursesndeep in this case.k = n. The whole list is one block and is fully reversed.- Empty list and single node. Both must survive: the dummy node in Approach 3 means an empty list never dereferences
head. - Losing the rest of the list. Forgetting to save
kth.nextbefore cutting, or forgetting to reattach it, silently truncates the list. Always recordnextBlockStartfirst. - Values versus nodes. The brute force moves values and leaves node identities in place; the other two move nodes. Tests that compare value sequences accept both, but a judge that checks node identity (or a caller holding a node reference) will notice.
Related problems
- Reverse a LL is the single-block special case and the helper used here.
- Reverse a LinkedList, iterative version explains the three-pointer loop in detail.
- Rotate a LL is another rearrangement that cuts and reconnects a list.
- Remove Nth node from the back of the LL also relies on walking a fixed number of steps ahead with a dummy node.