25. Reverse Nodes in k-Group
Problem Description
You are given the head of a linked list and a positive integer k
. Your task is to reverse the nodes of the linked list in groups of k
nodes at a time, then return the modified list.
The reversal should work as follows:
- Take
k
consecutive nodes from the linked list and reverse their order - Move to the next group of
k
nodes and reverse them - Continue this process for all complete groups of
k
nodes
Important constraints and rules:
k
is guaranteed to be less than or equal to the length of the linked list- If the remaining nodes at the end of the list are fewer than
k
, leave them in their original order (don't reverse incomplete groups) - You must modify the actual node connections, not just swap the values stored in the nodes
- Only the links between nodes can be changed; the values within nodes must remain unchanged
For example, if you have a linked list 1 -> 2 -> 3 -> 4 -> 5
and k = 2
:
- First group: reverse
1 -> 2
to get2 -> 1
- Second group: reverse
3 -> 4
to get4 -> 3
- Remaining nodes:
5
is less thank
nodes, so leave it as is - Final result:
2 -> 1 -> 4 -> 3 -> 5
The solution involves identifying groups of k
nodes, reversing each complete group while maintaining proper connections between the reversed groups and any remaining nodes.
Intuition
When we need to reverse nodes in groups, the key insight is that we're essentially performing multiple smaller reversals and then connecting them together. Think of it like reversing words in a sentence - we reverse each word individually, then put them back in their original positions.
The first challenge is determining whether we have enough nodes for a complete group. We need to look ahead k
nodes before committing to a reversal. If we can't find k
nodes, we know we've reached the end and should stop.
For the reversal itself, we can treat each group of k
nodes as an independent linked list segment. By temporarily "cutting" this segment from the main list (setting the last node's next
to None
), we can reverse it using a standard linked list reversal technique. The beauty of this approach is that we can reuse a simple reversal function for each group.
The trickiest part is maintaining the connections between reversed groups. After reversing a group, we need to:
- Connect the previous group's tail to the new head of the reversed group
- Connect the tail of the reversed group to the rest of the list
- Move our pointer to the end of the reversed group to prepare for the next iteration
Using a dummy node simplifies edge cases, particularly when reversing the first group. The dummy node acts as a stable reference point - its next
pointer will always point to the actual head of our result list, even after the first group is reversed.
The pattern emerges: for each iteration, we identify k
nodes, detach them, reverse them, and reattach them. We keep track of where to connect each reversed segment using a pre
pointer that marks the node just before the current group. After reversing, what was the first node of the group becomes the last, so that becomes our new pre
for the next iteration.
Learn more about Recursion and Linked List patterns.
Solution Approach
The implementation uses a simulation approach with a helper function for reversing linked lists. Let's walk through the solution step by step.
First, we define a helper function reverse
that reverses an entire linked list:
- Create a dummy node to build the reversed list
- Iterate through the original list, taking each node and inserting it at the beginning of the new list
- Each node's
next
pointer is updated to point to the previous head of the reversed list - Return the reversed list starting from
dummy.next
For the main function reverseKGroup
, we use the following approach:
-
Initialize pointers: Create a dummy node pointing to the original head. This dummy node helps handle edge cases and provides a stable reference to return the final result. We also initialize a
pre
pointer to track the node just before each group to be reversed. -
Check for k nodes: Before reversing any group, we need to verify that we have at least
k
nodes remaining:- Start from
pre
and movecur
pointerk
times forward - If
cur
becomesNone
before completingk
moves, we've reached the end with fewer thank
nodes remaining - Return
dummy.next
as we don't reverse incomplete groups
- Start from
-
Extract and reverse the group:
- Save the first node of the group as
node
(which ispre.next
) - Save the node after the group as
nxt
(which iscur.next
) - Temporarily disconnect the group by setting
cur.next = None
- Call
reverse(node)
to reverse thesek
nodes
- Save the first node of the group as
-
Reconnect the reversed group:
- Connect
pre.next
to the new head of the reversed group (returned byreverse
) - Connect the tail of the reversed group (which is the original
node
) tonxt
- Update
pre
tonode
for the next iteration
- Connect
-
Continue iteration: Repeat steps 2-4 until we can't find a complete group of
k
nodes.
The time complexity is O(n)
where n
is the number of nodes, as we visit each node a constant number of times. The space complexity is O(1)
as we only use a fixed number of pointers regardless of input size.
The elegance of this solution lies in treating each group as an independent unit that can be reversed and reconnected, while the dummy node and pointer management handle all the edge cases seamlessly.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example: linked list 1 -> 2 -> 3 -> 4 -> 5
with k = 3
.
Initial Setup:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 ^ pre
We create a dummy node pointing to head (1) and set pre = dummy
.
First Iteration - Check for k nodes:
Starting from pre
, we move a cur
pointer k times:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 ^ ^ pre cur
We successfully moved 3 times (1→2→3), so we have a complete group.
Extract and Reverse First Group:
- Save
node = pre.next
(node 1) - Save
nxt = cur.next
(node 4) - Set
cur.next = None
to isolate the group:1 -> 2 -> 3 -> None
- Reverse this group to get:
3 -> 2 -> 1 -> None
Reconnect:
Before: dummy -> [1 -> 2 -> 3] -> 4 -> 5 After: dummy -> [3 -> 2 -> 1] -> 4 -> 5
- Set
pre.next = 3
(head of reversed group) - Set
node.next = 4
(connect tail of reversed group to rest) - Update
pre = node
(node 1)
Second Iteration - Check for k nodes:
dummy -> 3 -> 2 -> 1 -> 4 -> 5 ^ pre
Starting from pre
(node 1), try to move cur
k times:
- Move 1:
cur
at node 4 - Move 2:
cur
at node 5 - Move 3:
cur
becomesNone
(out of bounds)
Since we can't find k nodes, we stop and return dummy.next
.
Final Result: 3 -> 2 -> 1 -> 4 -> 5
The first group (1, 2, 3) was reversed to (3, 2, 1), while the incomplete group (4, 5) remained unchanged.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import Optional
8
9class Solution:
10 def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
11 def reverse_linked_list(start_node: Optional[ListNode]) -> Optional[ListNode]:
12 """
13 Reverses a linked list and returns the new head.
14
15 Args:
16 start_node: Head of the linked list to reverse
17
18 Returns:
19 New head of the reversed linked list
20 """
21 dummy_head = ListNode()
22 current = start_node
23
24 # Reverse the linked list by inserting each node at the beginning
25 while current:
26 next_temp = current.next
27 current.next = dummy_head.next
28 dummy_head.next = current
29 current = next_temp
30
31 return dummy_head.next
32
33 # Create dummy node to simplify edge cases
34 dummy = ListNode(next=head)
35 prev_group_tail = dummy
36
37 while prev_group_tail:
38 # Check if there are k nodes remaining
39 current = prev_group_tail
40 for _ in range(k):
41 current = current.next
42 if current is None:
43 # Less than k nodes remaining, return result
44 return dummy.next
45
46 # Save pointers for group reversal
47 group_head = prev_group_tail.next
48 next_group_head = current.next
49
50 # Disconnect current group from the rest of the list
51 current.next = None
52
53 # Reverse the current group and connect it back
54 prev_group_tail.next = reverse_linked_list(group_head)
55
56 # After reversal, original head becomes the tail
57 group_head.next = next_group_head
58
59 # Move to the next group
60 prev_group_tail = group_head
61
62 return dummy.next
63
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Reverses nodes in k-group
14 * Given a linked list, reverse the nodes of a linked list k at a time
15 * @param head The head of the linked list
16 * @param k The group size for reversal
17 * @return The new head of the modified linked list
18 */
19 public ListNode reverseKGroup(ListNode head, int k) {
20 // Create a dummy node to simplify edge cases
21 ListNode dummy = new ListNode(0);
22 dummy.next = head;
23
24 // Pointer to track the node before the current group
25 ListNode previousGroupTail = dummy;
26
27 while (previousGroupTail != null) {
28 // Check if there are at least k nodes remaining
29 ListNode currentNode = previousGroupTail;
30 for (int i = 0; i < k; i++) {
31 currentNode = currentNode.next;
32 if (currentNode == null) {
33 // Less than k nodes remaining, return the result
34 return dummy.next;
35 }
36 }
37
38 // Save pointers for the reversal process
39 ListNode groupStart = previousGroupTail.next; // First node of current group
40 ListNode nextGroupStart = currentNode.next; // First node of next group
41
42 // Disconnect the current group from the rest of the list
43 currentNode.next = null;
44
45 // Reverse the current group and connect it back
46 previousGroupTail.next = reverse(groupStart);
47
48 // After reversal, groupStart becomes the tail of the reversed group
49 groupStart.next = nextGroupStart;
50
51 // Move previousGroupTail to the tail of the current reversed group
52 previousGroupTail = groupStart;
53 }
54
55 return dummy.next;
56 }
57
58 /**
59 * Helper method to reverse a linked list
60 * @param head The head of the linked list to reverse
61 * @return The new head of the reversed linked list
62 */
63 private ListNode reverse(ListNode head) {
64 // Use dummy node to build the reversed list
65 ListNode dummy = new ListNode();
66 ListNode current = head;
67
68 // Iterate through the list and prepend each node to dummy
69 while (current != null) {
70 ListNode nextNode = current.next; // Save the next node
71 current.next = dummy.next; // Point current to the reversed list
72 dummy.next = current; // Make current the new head of reversed list
73 current = nextNode; // Move to the next node
74 }
75
76 return dummy.next;
77 }
78}
79
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* reverseKGroup(ListNode* head, int k) {
14 // Create a dummy node pointing to head for easier manipulation
15 ListNode* dummyHead = new ListNode(0, head);
16
17 // Pointer to track the node before the current group
18 ListNode* prevGroupEnd = dummyHead;
19
20 while (prevGroupEnd != nullptr) {
21 // Check if there are at least k nodes remaining
22 ListNode* currentNode = prevGroupEnd;
23 for (int i = 0; i < k; i++) {
24 currentNode = currentNode->next;
25 if (currentNode == nullptr) {
26 // Less than k nodes remaining, return the result
27 return dummyHead->next;
28 }
29 }
30
31 // Save pointers for the current group
32 ListNode* groupStart = prevGroupEnd->next; // First node of current group
33 ListNode* nextGroupStart = currentNode->next; // First node of next group
34
35 // Disconnect the current group from the rest of the list
36 currentNode->next = nullptr;
37
38 // Reverse the current group and connect it back
39 prevGroupEnd->next = reverseList(groupStart);
40
41 // After reversal, groupStart becomes the last node of the reversed group
42 groupStart->next = nextGroupStart;
43
44 // Move prevGroupEnd to the end of the current reversed group
45 prevGroupEnd = groupStart;
46 }
47
48 return dummyHead->next;
49 }
50
51private:
52 // Helper function to reverse a linked list
53 ListNode* reverseList(ListNode* head) {
54 // Create a dummy node to simplify the reversal process
55 ListNode* dummyNode = new ListNode();
56 ListNode* current = head;
57
58 // Iterate through the list and reverse connections
59 while (current != nullptr) {
60 ListNode* nextNode = current->next; // Save the next node
61 current->next = dummyNode->next; // Point current to the previous reversed portion
62 dummyNode->next = current; // Update dummy to point to current
63 current = nextNode; // Move to the next node
64 }
65
66 return dummyNode->next;
67 }
68};
69
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Reverses nodes in k-group
15 * Given a linked list, reverse the nodes of the list k at a time and return the modified list.
16 * If the number of nodes is not a multiple of k, the remaining nodes at the end should remain as is.
17 *
18 * @param head - The head of the linked list
19 * @param k - The group size for reversing
20 * @returns The head of the modified linked list
21 */
22function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
23 // Create a dummy node pointing to head for easier manipulation
24 const dummyNode: ListNode = new ListNode(0, head);
25
26 // Pointer to track the node before the current group
27 let previousGroupTail: ListNode = dummyNode;
28
29 while (previousGroupTail !== null) {
30 // Find the kth node from the current position
31 let currentNode: ListNode | null = previousGroupTail;
32
33 // Move k steps forward to find the end of current group
34 for (let i = 0; i < k; i++) {
35 currentNode = currentNode?.next || null;
36
37 // If we can't form a complete group of k nodes, return the result
38 if (currentNode === null) {
39 return dummyNode.next;
40 }
41 }
42
43 // Save the first node of the current group (will become the last after reversal)
44 const currentGroupHead: ListNode | null = previousGroupTail.next;
45
46 // Save the first node of the next group
47 const nextGroupHead: ListNode | null = currentNode?.next || null;
48
49 // Disconnect the current group from the rest of the list
50 currentNode!.next = null;
51
52 // Reverse the current group and connect it to the previous part
53 previousGroupTail.next = reverse(currentGroupHead);
54
55 // Connect the reversed group to the next part of the list
56 currentGroupHead!.next = nextGroupHead;
57
58 // Move the pointer to the end of the reversed group for the next iteration
59 previousGroupTail = currentGroupHead!;
60 }
61
62 return dummyNode.next;
63}
64
65/**
66 * Reverses a linked list
67 *
68 * @param head - The head of the linked list to reverse
69 * @returns The new head of the reversed linked list
70 */
71function reverse(head: ListNode | null): ListNode | null {
72 // Previous node starts as null (will be the next of the new tail)
73 let previousNode: ListNode | null = null;
74
75 // Current node being processed
76 let currentNode: ListNode | null = head;
77
78 // Iterate through the list and reverse the pointers
79 while (currentNode !== null) {
80 // Store the next node before we change the pointer
81 const nextNode: ListNode | null = currentNode.next;
82
83 // Reverse the current node's pointer
84 currentNode.next = previousNode;
85
86 // Move previous pointer one step forward
87 previousNode = currentNode;
88
89 // Move current pointer one step forward
90 currentNode = nextNode;
91 }
92
93 // Previous node is now the new head of the reversed list
94 return previousNode;
95}
96
Time and Space Complexity
The time complexity is O(n)
, where n
is the total number of nodes in the linked list. This is because:
- The main while loop iterates through the list in groups of
k
nodes - For each group, we traverse
k
nodes to check if a complete group exists - If a complete group exists, we reverse those
k
nodes using thereverse
function, which takesO(k)
time - Each node is visited at most twice: once during the checking phase and once during the reversal phase
- Total operations:
O(n/k) * O(k) + O(n/k) * O(k) = O(n) + O(n) = O(n)
The space complexity is O(1)
because:
- The algorithm only uses a constant amount of extra space for pointers (
dummy
,pre
,cur
,node
,nxt
) - The
reverse
helper function also uses only a constant amount of extra space (dummy
,cur
,nxt
local variables) - No recursive calls are made that would use the call stack
- The reversal is done in-place by manipulating the existing node pointers
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Pointer Management After Reversal
One of the most common mistakes is forgetting that after reversing a group, the original head of that group becomes its tail. Many developers incorrectly update the prev
pointer or fail to properly reconnect the reversed group.
Incorrect approach:
# Wrong: Trying to use the new head as the connection point for the next group
prev_group_tail.next = reverse_linked_list(group_head)
prev_group_tail = prev_group_tail.next # This points to the new head, not the tail!
Correct approach:
# Right: Remember that group_head becomes the tail after reversal
prev_group_tail.next = reverse_linked_list(group_head)
group_head.next = next_group_head # group_head is now the tail
prev_group_tail = group_head # Move to the actual tail of the reversed group
Pitfall 2: Off-by-One Error When Counting k Nodes
Another frequent error occurs when checking if k nodes are available. Developers often start counting from the wrong node or include the previous group's tail in the count.
Incorrect approach:
# Wrong: Starting count from prev_group_tail itself
current = prev_group_tail
for i in range(k):
if current is None:
return dummy.next
current = current.next
Correct approach:
# Right: Start from prev_group_tail and move k times
current = prev_group_tail
for _ in range(k):
current = current.next
if current is None:
return dummy.next
Pitfall 3: Modifying the List While Checking for k Nodes
Some implementations try to reverse nodes while checking if k nodes exist, leading to corrupted list structures when there are fewer than k remaining nodes.
Incorrect approach:
# Wrong: Reversing while counting
count = 0
temp_head = None
current = prev_group_tail.next
while current and count < k:
next_node = current.next
current.next = temp_head
temp_head = current
current = next_node
count += 1
if count < k: # Too late! The list is already partially reversed
# Need to reverse back - extra work and error-prone
Correct approach:
# Right: Check first, then reverse
# First verify k nodes exist
current = prev_group_tail
for _ in range(k):
current = current.next
if current is None:
return dummy.next
# Only then proceed with reversal
group_head = prev_group_tail.next
next_group_head = current.next
current.next = None
prev_group_tail.next = reverse_linked_list(group_head)
Pitfall 4: Not Properly Isolating the Group Before Reversal
Forgetting to disconnect the group from the rest of the list before reversing can cause the reverse function to process more nodes than intended.
Incorrect approach:
# Wrong: Forgetting to set current.next = None
group_head = prev_group_tail.next
next_group_head = current.next
# current.next is still pointing to next_group_head!
prev_group_tail.next = reverse_linked_list(group_head) # This reverses beyond k nodes
Correct approach:
# Right: Properly isolate the group
group_head = prev_group_tail.next
next_group_head = current.next
current.next = None # Critical: disconnect the group
prev_group_tail.next = reverse_linked_list(group_head)
group_head.next = next_group_head # Reconnect after reversal
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!