Facebook Pixel

2074. Reverse Nodes in Even Length Groups

Problem Description

You are given the head of a singly-linked list. The task is to group the nodes sequentially into groups of increasing sizes (1, 2, 3, 4, ...) and reverse only the groups that have an even number of nodes.

The grouping works as follows:

  • Group 1: Contains the 1st node (size = 1)
  • Group 2: Contains the 2nd and 3rd nodes (size = 2)
  • Group 3: Contains the 4th, 5th, and 6th nodes (size = 3)
  • Group 4: Contains the 7th, 8th, 9th, and 10th nodes (size = 4)
  • And so on...

The pattern continues where group k contains k nodes. However, the last group might have fewer nodes than expected if there aren't enough nodes left in the list. The last group can have at most 1 + (size of second-to-last group) nodes.

Your task is to:

  1. Identify which groups have an even number of nodes
  2. Reverse the order of nodes within those even-length groups
  3. Keep odd-length groups unchanged
  4. Return the modified linked list

For example, if you have a linked list [1,2,3,4,5,6,7,8,9,10]:

  • Group 1: [1] (size 1, odd - no change)
  • Group 2: [2,3] (size 2, even - reverse to [3,2])
  • Group 3: [4,5,6] (size 3, odd - no change)
  • Group 4: [7,8,9,10] (size 4, even - reverse to [10,9,8,7])
  • Result: [1,3,2,4,5,6,10,9,8,7]

The challenge involves managing the pointers correctly while reversing specific segments of the linked list and handling the edge case where the last group might be incomplete.

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

Intuition

The key insight is that we need to process the linked list in chunks of increasing sizes (1, 2, 3, 4, ...) and selectively reverse only the even-sized chunks. This suggests a pattern-based traversal where we keep track of the current group size.

First, let's think about what we need:

  1. A way to determine how many nodes belong to each group
  2. A mechanism to reverse a specific segment of the linked list
  3. A way to connect the reversed segments back to the main list

The mathematical pattern for groups is straightforward - group k should have k nodes. The total number of nodes used up to and including group k is 1 + 2 + 3 + ... + k = k * (k + 1) / 2. This formula helps us determine when to stop creating full groups.

For the reversal process, we need a helper function that can reverse exactly l nodes starting from a given position and properly reconnect them. The trick is to keep track of:

  • The node before the segment to reverse (to reconnect the head of reversed segment)
  • The first node of the segment (which becomes the last after reversal)
  • The node after the segment (to reconnect the tail of reversed segment)

The main algorithm flow becomes:

  1. Count total nodes to handle the last group correctly
  2. Use a dummy node to simplify edge cases at the head
  3. For each group size l from 1 onwards:
    • Check if we have enough nodes for a complete group using the formula (1 + l) * l / 2 <= n
    • If l is even, reverse the next l nodes
    • Move the pointer forward by l positions
    • Increment l for the next group
  4. Handle the last group separately since it might be incomplete - calculate remaining nodes as n - l * (l - 1) / 2 and reverse if even

The elegance lies in treating the problem as a sequence of independent reverse operations on specific segments, where the segment sizes follow a predictable pattern and only even-sized segments need modification.

Learn more about Linked List patterns.

Solution Approach

The implementation uses a two-phase approach: first counting the total nodes, then processing groups with selective reversal.

Step 1: Count Total Nodes

n = 0
t = head
while t:
    t = t.next
    n += 1

We traverse the entire list once to get the total count n. This helps us determine when we've reached the last (potentially incomplete) group.

Step 2: Set Up Dummy Node

dummy = ListNode(0, head)
prev = dummy

A dummy node simplifies handling edge cases, especially when the first group needs reversal. The prev pointer tracks the node just before the current group.

Step 3: Helper Function for Reversal

def reverse(head, l):
    prev, cur, tail = None, head, head
    i = 0
    while cur and i < l:
        t = cur.next
        cur.next = prev
        prev = cur
        cur = t
        i += 1
    tail.next = cur
    return prev

This function reverses exactly l nodes starting from head:

  • tail saves the original head (which becomes the tail after reversal)
  • Standard reversal using three pointers: prev, cur, and temporary t
  • After reversing l nodes, tail.next = cur connects the reversed segment to the rest of the list
  • Returns the new head of the reversed segment

Step 4: Process Complete Groups

l = 1
while (1 + l) * l // 2 <= n and prev:
    if l % 2 == 0:
        prev.next = reverse(prev.next, l)
    i = 0
    while i < l and prev:
        prev = prev.next
        i += 1
    l += 1
  • Start with group size l = 1
  • Continue while we have enough nodes for a complete group: (1 + l) * l // 2 <= n
  • If group size is even (l % 2 == 0), reverse the next l nodes
  • Move prev pointer forward by l positions to reach the end of current group
  • Increment l for the next group

Step 5: Handle the Last Incomplete Group

left = n - l * (l - 1) // 2
if left > 0 and left % 2 == 0:
    prev.next = reverse(prev.next, left)
  • Calculate remaining nodes: left = n - l * (l - 1) // 2
  • The formula l * (l - 1) // 2 gives the total nodes used in all complete groups before group l
  • If there are remaining nodes and their count is even, reverse them

Step 6: Return Result

return dummy.next

Return the actual head (skipping the dummy node).

The time complexity is O(n) where we traverse the list twice - once for counting and once for processing. The space complexity is O(1) as we only use a constant amount of extra pointers.

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 a concrete example with the linked list: [1,2,3,4,5,6,7]

Initial Setup:

  • Count total nodes: n = 7
  • Create dummy node pointing to head: dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
  • Set prev = dummy

Group 1 (size = 1):

  • Check: Can we form a complete group? (1+1)*1/2 = 1 ≤ 7
  • Is group size even? 1 % 2 = 1 (odd) → No reversal
  • Move prev forward 1 position: prev now points to node 1
  • Increment group size: l = 2

Group 2 (size = 2):

  • Check: Can we form a complete group? (1+2)*2/2 = 3 ≤ 7
  • Is group size even? 2 % 2 = 0 (even) → Reverse!
  • Before reversal: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
  • Call reverse(node_2, 2) to reverse nodes [2,3]
    • Save tail = 2 (will become last)
    • Reverse: 3 -> 2
    • Connect: 2 -> 4
  • After reversal: 1 -> 3 -> 2 -> 4 -> 5 -> 6 -> 7
  • Move prev forward 2 positions: prev now points to node 2
  • Increment group size: l = 3

Group 3 (size = 3):

  • Check: Can we form a complete group? (1+3)*3/2 = 6 ≤ 7
  • Is group size even? 3 % 2 = 1 (odd) → No reversal
  • List remains: 1 -> 3 -> 2 -> 4 -> 5 -> 6 -> 7
  • Move prev forward 3 positions: prev now points to node 6
  • Increment group size: l = 4

Group 4 (size = 4):

  • Check: Can we form a complete group? (1+4)*4/2 = 10 > 7
  • Exit the main loop

Handle Last Incomplete Group:

  • Calculate remaining nodes: left = 7 - 3*(3-1)/2 = 7 - 3 = 1
  • The last group has only 1 node: [7]
  • Is count even? 1 % 2 = 1 (odd) → No reversal

Final Result: [1,3,2,4,5,6,7]

The groups were:

  • Group 1: [1] - size 1 (odd) → unchanged
  • Group 2: [2,3] - size 2 (even) → reversed to [3,2]
  • Group 3: [4,5,6] - size 3 (odd) → unchanged
  • Group 4: [7] - size 1 (odd, incomplete) → 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
7class Solution:
8    def reverseEvenLengthGroups(self, head: Optional[ListNode]) -> Optional[ListNode]:
9        def reverse_group(group_head: Optional[ListNode], group_size: int) -> Optional[ListNode]:
10            """
11            Reverses a group of nodes starting from group_head with specified size.
12            Returns the new head of the reversed group.
13            """
14            prev_node = None
15            current_node = group_head
16            tail_node = group_head  # Will become the tail after reversal
17          
18            nodes_reversed = 0
19            while current_node and nodes_reversed < group_size:
20                # Store next node before breaking the link
21                next_temp = current_node.next
22                # Reverse the link
23                current_node.next = prev_node
24                # Move pointers forward
25                prev_node = current_node
26                current_node = next_temp
27                nodes_reversed += 1
28          
29            # Connect the tail of reversed group to remaining list
30            tail_node.next = current_node
31            return prev_node  # New head of the reversed group
32      
33        # Count total number of nodes in the linked list
34        total_nodes = 0
35        temp_node = head
36        while temp_node:
37            temp_node = temp_node.next
38            total_nodes += 1
39      
40        # Create dummy node to simplify edge cases
41        dummy_node = ListNode(0, head)
42        prev_group_tail = dummy_node
43      
44        # Process groups of increasing size (1, 2, 3, ...)
45        group_size = 1
46        while (1 + group_size) * group_size // 2 <= total_nodes and prev_group_tail:
47            # Reverse only even-length groups
48            if group_size % 2 == 0:
49                prev_group_tail.next = reverse_group(prev_group_tail.next, group_size)
50          
51            # Move prev_group_tail to the end of current group
52            nodes_traversed = 0
53            while nodes_traversed < group_size and prev_group_tail:
54                prev_group_tail = prev_group_tail.next
55                nodes_traversed += 1
56          
57            group_size += 1
58      
59        # Handle the last group (which might be incomplete)
60        # Calculate remaining nodes in the last group
61        nodes_processed = group_size * (group_size - 1) // 2
62        remaining_nodes = total_nodes - nodes_processed
63      
64        # Reverse the last group if it has even length
65        if remaining_nodes > 0 and remaining_nodes % 2 == 0:
66            prev_group_tail.next = reverse_group(prev_group_tail.next, remaining_nodes)
67      
68        return dummy_node.next
69
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    public ListNode reverseEvenLengthGroups(ListNode head) {
13        // Calculate total number of nodes in the linked list
14        int totalNodes = 0;
15        for (ListNode current = head; current != null; current = current.next) {
16            totalNodes++;
17        }
18      
19        // Create dummy node to simplify edge cases
20        ListNode dummy = new ListNode(0, head);
21        ListNode previousGroupEnd = dummy;
22      
23        // Process groups of increasing size (1, 2, 3, ...)
24        int groupSize = 1;
25      
26        // Process complete groups
27        // Formula: sum of 1 to groupSize = groupSize * (groupSize + 1) / 2
28        while ((1 + groupSize) * groupSize / 2 <= totalNodes && previousGroupEnd != null) {
29            // Reverse groups with even length
30            if (groupSize % 2 == 0) {
31                ListNode groupStart = previousGroupEnd.next;
32                previousGroupEnd.next = reverse(groupStart, groupSize);
33            }
34          
35            // Move pointer to the end of current group
36            for (int i = 0; i < groupSize && previousGroupEnd != null; i++) {
37                previousGroupEnd = previousGroupEnd.next;
38            }
39          
40            groupSize++;
41        }
42      
43        // Handle the last incomplete group if it exists
44        // Calculate remaining nodes: total - sum of processed groups
45        int remainingNodes = totalNodes - groupSize * (groupSize - 1) / 2;
46      
47        // Reverse the last group if it has even number of nodes
48        if (remainingNodes > 0 && remainingNodes % 2 == 0) {
49            ListNode lastGroupStart = previousGroupEnd.next;
50            previousGroupEnd.next = reverse(lastGroupStart, remainingNodes);
51        }
52      
53        return dummy.next;
54    }
55  
56    /**
57     * Reverses a portion of linked list starting from head with length l
58     * @param head The starting node of the portion to reverse
59     * @param l The number of nodes to reverse
60     * @return The new head of the reversed portion
61     */
62    private ListNode reverse(ListNode head, int l) {
63        ListNode previous = null;
64        ListNode current = head;
65        ListNode originalHead = current;  // Save original head to connect with remaining list
66        int count = 0;
67      
68        // Reverse l nodes
69        while (current != null && count < l) {
70            ListNode nextNode = current.next;
71            current.next = previous;
72            previous = current;
73            current = nextNode;
74            count++;
75        }
76      
77        // Connect the tail of reversed portion to the remaining list
78        originalHead.next = current;
79      
80        // Return new head of reversed portion
81        return previous;
82    }
83}
84
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 */
11
12class Solution {
13public:
14    /**
15     * Reverses even-length groups in a linked list.
16     * Groups are formed with sizes 1, 2, 3, ... and so on.
17     * Only groups with even length are reversed.
18     * 
19     * @param head - The head of the linked list
20     * @return The head of the modified linked list
21     */
22    ListNode* reverseEvenLengthGroups(ListNode* head) {
23        // Extract all values from the linked list into a vector
24        vector<int> values;
25        ListNode* current_node = head;
26      
27        while (current_node != nullptr) {
28            values.push_back(current_node->val);
29            current_node = current_node->next;
30        }
31      
32        int total_length = values.size();
33      
34        // Process groups of increasing size (1, 2, 3, ...)
35        int start_index = 0;
36        int group_size = 1;
37      
38        while (start_index < total_length) {
39            // Handle the last group which might be smaller than expected
40            int actual_group_size = min(total_length - start_index, group_size);
41          
42            // Check if the group has even length (using bitwise AND to check if odd)
43            bool is_even_length = (actual_group_size & 1) == 0;
44          
45            if (is_even_length) {
46                // Reverse the elements in the current group directly in the vector
47                reverse(values.begin() + start_index, 
48                       values.begin() + start_index + actual_group_size);
49            }
50          
51            // Move to the next group
52            start_index += actual_group_size;
53            group_size++;
54        }
55      
56        // Update the linked list with the modified values
57        current_node = head;
58        for (int value : values) {
59            if (current_node != nullptr) {
60                current_node->val = value;
61                current_node = current_node->next;
62            }
63        }
64      
65        return head;
66    }
67};
68
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 even-length groups in a linked list.
15 * Groups are formed with sizes 1, 2, 3, ... and so on.
16 * Only groups with even length are reversed.
17 * 
18 * @param head - The head of the linked list
19 * @returns The head of the modified linked list
20 */
21function reverseEvenLengthGroups(head: ListNode | null): ListNode | null {
22    // Extract all values from the linked list into an array
23    const values: number[] = [];
24    let currentNode: ListNode | null = head;
25  
26    while (currentNode !== null) {
27        values.push(currentNode.val);
28        currentNode = currentNode.next;
29    }
30
31    const totalLength: number = values.length;
32  
33    // Process groups of increasing size (1, 2, 3, ...)
34    let startIndex: number = 0;
35    let groupSize: number = 1;
36  
37    while (startIndex < totalLength) {
38        // Handle the last group which might be smaller than expected
39        const actualGroupSize: number = Math.min(totalLength - startIndex, groupSize);
40      
41        // Check if the group has even length (using bitwise AND to check if odd)
42        const isEvenLength: boolean = (actualGroupSize & 1) === 0;
43      
44        if (isEvenLength) {
45            // Extract the group, reverse it, and insert it back
46            const groupToReverse: number[] = values.splice(startIndex, actualGroupSize);
47            groupToReverse.reverse();
48            values.splice(startIndex, 0, ...groupToReverse);
49        }
50      
51        // Move to the next group
52        startIndex += actualGroupSize;
53        groupSize++;
54    }
55
56    // Update the linked list with the modified values
57    currentNode = head;
58    for (const value of values) {
59        if (currentNode !== null) {
60            currentNode.val = value;
61            currentNode = currentNode.next;
62        }
63    }
64  
65    return head;
66}
67

Time and Space Complexity

Time Complexity: O(n)

The algorithm traverses the linked list multiple times:

  • First pass counts the total number of nodes: O(n)
  • Main loop processes groups of increasing sizes (1, 2, 3, ..., k) where the sum 1 + 2 + 3 + ... + k ≈ n
  • For each group that needs reversing (even-length groups), the reverse function takes O(group_size) time
  • The total work done across all groups is bounded by the total number of nodes since each node is visited at most twice (once during normal traversal, once during reversal if in an even-length group)
  • Overall: O(n) + O(n) = O(n)

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Variables like dummy, prev, l, i, left, n are all single values
  • The reverse function uses local variables prev, cur, tail, t, i which don't scale with input size
  • No recursive calls are made, so no additional call stack space is used
  • The algorithm modifies the linked list in-place without creating new nodes

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrectly Calculating the Last Group's Size

One of the most common mistakes is miscalculating how many nodes remain in the last group. Developers often forget that the last group can be incomplete and might use the wrong formula to determine its size.

Pitfall Example:

# WRONG: Assuming the last group always has size 'group_size'
remaining_nodes = group_size  # This is incorrect!

Correct Approach:

# Calculate total nodes used in all complete groups
nodes_processed = group_size * (group_size - 1) // 2
# Calculate actual remaining nodes
remaining_nodes = total_nodes - nodes_processed

2. Losing Nodes During Reversal

When reversing a group, if you don't properly connect the reversed segment back to the rest of the list, you can lose all nodes after that group.

Pitfall Example:

def reverse_group(head, size):
    prev, cur = None, head
    for _ in range(size):
        next_temp = cur.next
        cur.next = prev
        prev = cur
        cur = next_temp
    # WRONG: Forgot to connect the tail to remaining nodes!
    return prev  # This loses all nodes after the reversed group

Correct Approach:

def reverse_group(head, size):
    prev, cur = None, head
    tail = head  # Save the original head (becomes tail after reversal)
    for _ in range(size):
        next_temp = cur.next
        cur.next = prev
        prev = cur
        cur = next_temp
    tail.next = cur  # Critical: Connect reversed group to remaining list
    return prev

3. Not Updating the Previous Group's Connection

After reversing a group, you must update the previous group's last node to point to the new head of the reversed group.

Pitfall Example:

# WRONG: Just reversing without updating connections
if group_size % 2 == 0:
    reverse_group(current_head, group_size)  # Lost the returned new head!

Correct Approach:

if group_size % 2 == 0:
    prev_group_tail.next = reverse_group(prev_group_tail.next, group_size)

4. Traversing Past the End of a Group

When moving to the next group, ensure you traverse exactly the current group's size, not more or less.

Pitfall Example:

# WRONG: Always moving by group_size without checking if nodes exist
for _ in range(group_size):
    prev_group_tail = prev_group_tail.next  # Can cause NullPointer

Correct Approach:

nodes_traversed = 0
while nodes_traversed < group_size and prev_group_tail:
    prev_group_tail = prev_group_tail.next
    nodes_traversed += 1

5. Off-by-One Error in Group Size Calculation

The formula for calculating how many nodes have been used up to group k is critical. Using the wrong formula leads to incorrect group boundaries.

Pitfall Example:

# WRONG formulas that are commonly attempted:
total_used = group_size * group_size // 2  # Missing the arithmetic series
total_used = (group_size * (group_size + 1)) // 2  # This includes current group

Correct Approach:

# For groups 1 through (k-1), total nodes = k*(k-1)/2
total_used_before_current = group_size * (group_size - 1) // 2
# For groups 1 through k, total nodes = k*(k+1)/2
total_including_current = (1 + group_size) * group_size // 2

Solution to Avoid These Pitfalls:

  1. Always use a dummy node to simplify edge cases
  2. Count total nodes first to know exactly how many nodes you're working with
  3. Test with small examples (like 1-10 nodes) and manually verify group boundaries
  4. Carefully track pointers and ensure all connections are maintained after reversal
  5. Use clear variable names that indicate their purpose (e.g., prev_group_tail instead of just prev)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More