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:
- Identify which groups have an even number of nodes
- Reverse the order of nodes within those even-length groups
- Keep odd-length groups unchanged
- 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.
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:
- A way to determine how many nodes belong to each group
- A mechanism to reverse a specific segment of the linked list
- 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:
- Count total nodes to handle the last group correctly
- Use a dummy node to simplify edge cases at the head
- 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 nextl
nodes - Move the pointer forward by
l
positions - Increment
l
for the next group
- Check if we have enough nodes for a complete group using the formula
- 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 temporaryt
- 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 nextl
nodes - Move
prev
pointer forward byl
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 groupl
- 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 EvaluatorExample 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 node1
- 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
- Save
- After reversal:
1 -> 3 -> 2 -> 4 -> 5 -> 6 -> 7
- Move
prev
forward 2 positions:prev
now points to node2
- 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 node6
- 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 takesO(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 variablesprev
,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:
- Always use a dummy node to simplify edge cases
- Count total nodes first to know exactly how many nodes you're working with
- Test with small examples (like 1-10 nodes) and manually verify group boundaries
- Carefully track pointers and ensure all connections are maintained after reversal
- Use clear variable names that indicate their purpose (e.g.,
prev_group_tail
instead of justprev
)
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!