Facebook Pixel

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 get 2 -> 1
  • Second group: reverse 3 -> 4 to get 4 -> 3
  • Remaining nodes: 5 is less than k 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Connect the previous group's tail to the new head of the reversed group
  2. Connect the tail of the reversed group to the rest of the list
  3. 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:

  1. 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.

  2. Check for k nodes: Before reversing any group, we need to verify that we have at least k nodes remaining:

    • Start from pre and move cur pointer k times forward
    • If cur becomes None before completing k moves, we've reached the end with fewer than k nodes remaining
    • Return dummy.next as we don't reverse incomplete groups
  3. Extract and reverse the group:

    • Save the first node of the group as node (which is pre.next)
    • Save the node after the group as nxt (which is cur.next)
    • Temporarily disconnect the group by setting cur.next = None
    • Call reverse(node) to reverse these k nodes
  4. Reconnect the reversed group:

    • Connect pre.next to the new head of the reversed group (returned by reverse)
    • Connect the tail of the reversed group (which is the original node) to nxt
    • Update pre to node for the next iteration
  5. 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 Evaluator

Example 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 becomes None (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 the reverse function, which takes O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More