LeetCode #25 Hard

Reverse Nodes in k-Group

Reverse the list k nodes at a time; a final group shorter than k stays as-is.

Constraints
  • The number of nodes in the list is n.
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000
linked-listrecursion
Open on LeetCode ↗
02

Intuition

Reverse nodes in k group takes a linked list and reverses it in blocks of k, leaving any final block shorter than k untouched. Both halves of that sentence carry weight: the reversal is routine, and the leftover rule is what makes the problem Hard. The reversal itself is the standard three-pointer flip — walk the list moving each node's next to point backwards. Doing it for exactly k nodes is a small variation. The real work is the bookkeeping around each block. Before reversing anything you must know whether k nodes actually exist ahead, because a short tail must be left alone: - Probe forward k steps first; if you run off the end, stop and leave the rest as it is. Reversing first and then discovering the block was short means undoing work, which is why the probe comes first. The second difficulty is reconnection. Reversing a block turns its first node into its last, so the previous block must now point at what was the block's last node, and the reversed block must point onward at whatever follows. A dummy node before the head removes the special case where the first block changes the list's head, and a groupPrev anchor tracks the node that needs rewiring after each block. A neat trick avoids fixing the forward link afterwards: initialise the reversal's prev to the node after the block, so the block already points onward as it is being flipped.

How to spot this pattern

Reverse-linked-list applied in blocks, with two additions: probe ahead to confirm a full group exists before touching anything, and use a dummy head so the first group needs no special case. The probe-then-act discipline is the general lesson — verify the whole operation is possible before performing any of it.

03

Approach

Try it first

Before reading on: price up what the direct approach costs here, then ask which pointers must be saved before you overwrite anything. Aim for O(n) time and O(1) space.

1

Use a dummy node before the head

Allocate a dummy whose next is the head, and return dummy.next at the end. The first block's reversal changes the head, and the dummy means that case needs no separate handling — a pattern worth reaching for whenever the head might move.

2

Probe for k nodes before reversing

From groupPrev, walk k steps to find the block's last node. If you hit null first, fewer than k nodes remain — return immediately, leaving the tail in its original order as the problem requires.

3

Reverse exactly k nodes

Run the standard prev/curr flip for k iterations. Initialise prev to the node after the block, not to null, so the reversed block's tail already points at the next block and no fix-up pass is needed.

4

Reconnect both ends

The block's original first node is now its last. Set groupPrev.next to the block's new first node, which is the node the probe found, then move groupPrev to that original first node — it is the anchor for the next block.

5

Repeat until the probe fails

Loop from the new groupPrev. Each iteration handles one block, and the loop ends naturally when the probe runs off the list, at which point the remaining short group is already correct.

6

Cost of the block reversal

Each node is visited by the probe once and by the reversal once, giving O(n) time with a constant factor of two. Space is O(1) for the iterative version — the recursive alternative is shorter to write but costs O(n/k) stack frames.

04

Solution & live demo

▶1class Solution:
▶2 def reverseKGroup(self, head, k):
▶3 dummy = ListNode(0, head)
▶4 group_prev = dummy
▶5 while True:
▶6 kth = group_prev
▶7 for _ in range(k): # probe
▶8 kth = kth.next
▶9 if not kth:
▶10 return dummy.next
▶11 group_next = kth.next
▶12 prev, curr = group_next, group_prev.next
▶13 while curr is not group_next: # reverse k nodes
▶14 nxt = curr.next
▶15 curr.next = prev
▶16 prev = curr
▶17 curr = nxt
▶18 tmp = group_prev.next # reconnect
▶19 group_prev.next = kth
▶20 group_prev = tmp
05

Common pitfalls

Reversing before confirming the group is complete

✗ Wrong
for _ in range(k):
    # reverse as you go
✓ Right
kth = group_prev
for _ in range(k):
    kth = kth.next
    if not kth: return dummy.next

A trailing partial group must be left untouched, but once you've started reversing you can't cheaply undo it. Probing first means you only commit when the full group is guaranteed.

Seeding prev with None instead of group_next

✗ Wrong
prev, curr = None, group_prev.next
✓ Right
prev, curr = group_next, group_prev.next

The reversed block's tail must point at the next group, not at null — otherwise the list is severed after the first block and everything beyond it is lost. Seeding with group_next splices the remainder on automatically.

Advancing group_prev after rewiring it

✗ Wrong
group_prev.next = kth
group_prev = group_prev.next
✓ Right
tmp = group_prev.next
group_prev.next = kth
group_prev = tmp

Reversal makes the group's old head its new tail, and that node is where the next group attaches. Reading group_prev.next after the rewire gives kth — the new head — so the pointer jumps to the wrong end of the block.

06

Edge cases

Length not divisible by k

The probe fails on the last group and it is left in original order.

k = 1

Every probe succeeds but reversal of one node is identity — list unchanged.

07

Complexity

Time
O(n)
Space
O(1)
Each node is visited twice (probe + flip).