Reverse Nodes in k-Group

HardLinked ListLinked ListRecursion

Problem

You are given the head of a linked list and an integer k. Reverse the nodes k at a time and return the new head. If the nodes remaining at the end of the list number fewer than k, leave that final partial group exactly as it was.

Example. With 1 -> 2 -> 3 -> 4 -> 5 and k = 2, the result is 2 -> 1 -> 4 -> 3 -> 5: the trailing single node 5 does not form a full group, so it is left untouched.

Key idea

This builds on ordinary full-list reversal but applies it repeatedly to fixed-size chunks and reconnects them correctly. For each group: walk ahead k nodes to confirm a full group exists; if fewer than k remain, stop and leave the rest as is. If confirmed, reverse that chunk's internal links with the same pointer-flipping technique as a full reversal. Then connect the tail of whatever came before to the reversed chunk's new head, and connect the chunk's own tail (originally its first node, now last) forward to where the next group begins.

This works iteratively, tracking the previous group's tail as a moving anchor, or recursively, where each call reverses one group and hands off the rest.

Solution

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val = 0, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
  const dummy = new ListNode(0, head);
  let groupPrev: ListNode = dummy;

  while (true) {
    let kth: ListNode | null = groupPrev;
    for (let i = 0; i < k && kth !== null; i++) {
      kth = kth.next; // walk ahead k nodes to check a full group exists
    }
    if (kth === null) {
      break; // fewer than k nodes remain, leave this final partial group untouched
    }

    const groupNext = kth.next;

    let prev: ListNode | null = groupNext; // seed with groupNext so the reversed tail links forward
    let curr: ListNode | null = groupPrev.next;
    while (curr !== groupNext) {
      const next: ListNode | null = curr!.next;
      curr!.next = prev;
      prev = curr;
      curr = next;
    }

    const newGroupPrev = groupPrev.next as ListNode;
    groupPrev.next = kth; // kth is now the group's head after reversal
    groupPrev = newGroupPrev;
  }

  return dummy.next;
}

Complexity

  • Time: O(n). Every node is examined a constant number of times: once to verify group length, once to reverse.
  • Space: O(1) iteratively; O(n / k) recursively, due to one stack frame per group.

Watch out for

  • Always verify a full group of k nodes exists before reversing any links: reversing first and discovering a short group afterward corrupts the list.
  • After reversal, the group's original first node is now last, and that node must be linked forward to the next group.
  • A dummy node before the head simplifies connecting the first reversed group, which otherwise has no predecessor.

Pattern

This composes the basic pointer-reversal pattern with chunking and relinking, the same building block behind Reverse Linked List and Reorder List, extended to operate on group boundaries rather than the whole list.

Related questions