DSAPrep
HardLinked List

Reverse Nodes in K Group

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 the left-out nodes at the end should remain as they are.

You may not alter the values in the list's nodes — only the nodes themselves may be rearranged.

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?
View original on LeetCode ↗

This extends the classic “reverse a linked list” pointer-flipping technique, applied one group of k nodes at a time instead of to the whole list. The extra bookkeeping is stitching each reversed group back onto the node that came before it — and correctly leaving a final short group (fewer than k nodes) untouched.

Recursive

Time O(n)Space O(n/k)

First check whether at least k nodes remain from the current position — if not, this is a trailing partial group, so return it unreversed. Otherwise, reverse exactly k nodes using the standard iterative reversal, then recursively solve the rest of the list and attach it after the original head (which is now the tail of this reversed group).

class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverseKGroup(self, head: ListNode | None, k: int) -> ListNode | None:
node = head
count = 0
while node and count < k:
node = node.next
count += 1
if count < k:
return head # fewer than k nodes left: leave them as-is
prev, curr = None, head
for _ in range(k):
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
head.next = self.reverseKGroup(curr, k) # head is now the tail of this group
return prev

Clean and mirrors the group structure directly, but the recursion creates one stack frame per group, so the call stack grows to O(n/k) frames — the iterative version below avoids that to satisfy the O(1) follow-up.

Iterative

OptimalTime O(n)Space O(1)

Use a dummy node before head and a group_prev pointer that always sits just before the group currently being reversed. For each group: first scan ahead k nodes to confirm a full group exists (returning early if not), then reverse that group in place using the same three-pointer technique, and finally reconnect group_prev to the new head of the reversed group before advancing group_prev to what is now the group’s tail.

class Solution:
def reverseKGroup(self, head: ListNode | None, k: int) -> ListNode | None:
dummy = ListNode(0, head)
group_prev = dummy
while True:
kth = group_prev
for _ in range(k):
kth = kth.next
if not kth:
return dummy.next # fewer than k nodes left: done
group_next = kth.next # first node after this group
prev, curr = group_next, group_prev.next
while curr != group_next:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
tmp = group_prev.next # this group's original head, now its tail
group_prev.next = kth # kth is now this group's head
group_prev = tmp

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

D
1
2
3
4
5
group_prev
1 / 5

Scan ahead k=2 nodes from group_prev (dummy): lands on node 2. A full group [1,2] exists — reverse it.

Why it’s correct: each full group is reversed with the same proven-correct in-place reversal, and group_prev always points to the node immediately before the next group to process — so reconnecting group_prev.next to the reversed group’s new head preserves the chain between groups. The upfront k-node lookahead guarantees a trailing partial group is detected and left untouched, exactly matching the problem’s requirement. Complexity: each node is visited a constant number of times — once during the lookahead scan and once during reversal — across all groups → O(n) time. Only a fixed number of pointer variables are used, with no recursion or auxiliary data structure → O(1) extra space — this satisfies the follow-up and is optimal, since every node’s next pointer must be touched at least once to reverse it.