Reverse Nodes in Even Length Groups
MedPrompt
You are given the head of a linked list.
The nodes in the linked list are sequentially assigned to non-empty groups whose lengths form the sequence of the natural numbers (1, 2, 3, 4, ...). The length of a group is the number of nodes assigned to it. In other words,
- The
1stnode is assigned to the first group. - The
2ndand the3rdnodes are assigned to the second group. - The
4th,5th, and6thnodes are assigned to the third group, and so on.
Note that the length of the last group may be less than or equal to 1 + the length of the second to last group.
Reverse the nodes in each group with an even length, and return the head of the modified linked list.
Example 1:
Input: head = [5,2,6,3,9,1,7,3,8,4]
Output: [5,6,2,3,9,1,4,8,3,7]
Explanation:
- The length of the first group is 1, which is odd, hence no reversal occurs.
- The length of the second group is 2, which is even, hence the nodes are reversed.
- The length of the third group is 3, which is odd, hence no reversal occurs.
- The length of the last group is 4, which is even, hence the nodes are reversed.Example 2:
Input: head = [1,1,0,6]
Output: [1,0,1,6]
Explanation:
- The length of the first group is 1. No reversal occurs.
- The length of the second group is 2. The nodes are reversed.
- The length of the last group is 1. No reversal occurs.Example 3:
Input: head = [1,1,0,6,5]
Output: [1,0,1,5,6]
Explanation:
- The length of the first group is 1. No reversal occurs.
- The length of the second group is 2. The nodes are reversed.
- The length of the last group is 2. The nodes are reversed.
Constraints:
- The number of nodes in the list is in the range
[1, 105]. 0 <= Node.val <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach simplifies the problem by converting the linked list into a more flexible data structure like an array or ArrayList. We first traverse the linked list and store all its node values. Then, we can easily access and manipulate groups of elements by their indices. After reversing the values of even-length groups within the array, we iterate through the original linked list again, updating each node's value from the modified array.
Algorithm
- Create an
ArrayListof integers. - Traverse the linked list from head to tail, adding each node's
valto theArrayList. - Initialize a
group_len = 1andstart_index = 0. - Loop while
start_indexis less than the size of the list:- Calculate the
end_indexfor the current group:min(start_index + group_len, list_size). - Calculate the
actual_lenof the group:end_index - start_index. - If
actual_lenis even, reverse the sublist of theArrayListfromstart_indextoend_index - 1. - Update
start_indexfor the next group:start_index += group_len. - Increment
group_len.
- Calculate the
- Traverse the original linked list again, and for each node, update its
valwith the corresponding value from the modifiedArrayList. - Return the original
head.
Walkthrough
The core idea is to trade space for simplicity. By moving the data to an array, we get O(1) access to any element, which makes grouping and reversing sub-sections much easier than manipulating pointers in a linked list.
import java.util.ArrayList;import java.util.Collections;import java.util.List; /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode reverseEvenLengthGroups(ListNode head) { if (head == null) { return null; } // 1. Convert linked list to ArrayList List<Integer> values = new ArrayList<>(); ListNode current = head; while (current != null) { values.add(current.val); current = current.next; } int n = values.size(); int groupLen = 1; int i = 0; // 2. Process groups in the ArrayList while (i < n) { int remaining = n - i; int currentGroupLen = Math.min(groupLen, remaining); if (currentGroupLen % 2 == 0) { // Reverse the sublist for the even length group int left = i; int right = i + currentGroupLen - 1; while (left < right) { int temp = values.get(left); values.set(left, values.get(right)); values.set(right, temp); left++; right--; } } i += groupLen; groupLen++; } // 3. Update the original linked list with modified values current = head; int j = 0; while (current != null) { current.val = values.get(j++); current = current.next; } return head; }}Complexity
Time
O(N), where N is the number of nodes. The conversion to an array takes O(N), processing the array takes O(N) (as each element is part of one reversal at most), and updating the list values takes another O(N). Total is O(N).
Space
O(N) to store the node values in the `ArrayList`.
Trade-offs
Pros
Simpler logic and implementation compared to in-place pointer manipulation.
Random access to elements makes sublist reversal straightforward.
Cons
Requires extra space proportional to the number of nodes in the list, which can be significant for large lists.
Involves three passes over the data (list to array, process array, array to list), whereas an in-place solution needs only one pass.
This is the optimal approach, modifying the linked list directly without using any significant extra storage. It involves a single pass through the list, where we identify groups, check their lengths, and perform reversals on the spot by carefully manipulating the next pointers of the nodes. This avoids the overhead of creating and populating an auxiliary data structure.
Algorithm
- Use a
group_prevpointer to mark the node just before the current group begins. To handle all cases uniformly, we can use a dummy node pointing to thehead, sogroup_prevstarts at the dummy node. - Initialize
group_len = 1. - Iterate as long as there are nodes to process (
group_prev.next != null). - In each iteration, find the tail of the current group,
group_tail, and count itsactual_len. Start fromgroup_prevand advance a pointergroup_lentimes or until the end of the list is reached. - If
actual_lenis even:- The group to be reversed starts at
group_prev.nextand ends atgroup_tail. - Perform a standard in-place reversal on this sublist.
- Carefully reconnect the pointers:
group_prevshould point to the new head of the reversed sublist (the originalgroup_tail), and the new tail of the sublist (the originalgroup_start) should point to the start of the next group. - Update
group_prevfor the next iteration to be the new tail of the just-processed group.
- The group to be reversed starts at
- If
actual_lenis odd:- No reversal is needed.
- Simply advance
group_prevtogroup_tailto prepare for the next group.
- Increment
group_lenfor the next iteration. - Return
dummy.next.
Walkthrough
We iterate through the list, keeping track of the end of the previous group. For each new group, we first determine its actual size. If the size is even, we perform a standard sublist reversal. The key challenge is to correctly re-link the reversed sublist with the rest of the list. A dummy node is used to simplify the handling of the first group.
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode reverseEvenLengthGroups(ListNode head) { ListNode dummy = new ListNode(0, head); ListNode groupPrev = dummy; int groupLen = 1; while (groupPrev.next != null) { ListNode groupTail = groupPrev; int actualLen = 0; // Find the end of the current group and its actual length for (int i = 0; i < groupLen && groupTail.next != null; i++) { groupTail = groupTail.next; actualLen++; } if (actualLen % 2 == 0) { ListNode groupStart = groupPrev.next; ListNode nextGroupStart = groupTail.next; // Reverse the sublist in-place ListNode prev = null; ListNode curr = groupStart; ListNode tempTail = nextGroupStart; for (int i = 0; i < actualLen; i++) { ListNode nextTemp = curr.next; curr.next = tempTail; tempTail = curr; curr = nextTemp; } // Reconnect the list groupPrev.next = tempTail; // tempTail is the new head groupPrev = groupStart; // The original start is now the tail } else { // Move groupPrev to the end of the odd length group groupPrev = groupTail; } groupLen++; } return dummy.next; }}Complexity
Time
O(N), where N is the number of nodes. Each node is visited a constant number of times during the single pass.
Space
O(1), as we only use a few pointers for manipulation, regardless of the list size.
Trade-offs
Pros
Highly efficient in terms of memory, using only a constant amount of extra space.
Processes the list in a single pass, making it time-efficient.
Cons
The pointer manipulation logic is complex and can be prone to off-by-one errors or incorrect linking.
Harder to read, debug, and maintain compared to the array-based solution.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode reverseEvenLengthGroups ( ListNode head ) { int n = 0 ; for ( ListNode t = head ; t != null ; t = t . next ) { ++ n ; } ListNode dummy = new ListNode ( 0 , head ); ListNode prev = dummy ; int l = 1 ; for (; ( 1 + l ) * l / 2 <= n && prev != null ; ++ l ) { if ( l % 2 == 0 ) { ListNode node = prev . next ; prev . next = reverse ( node , l ); } for ( int i = 0 ; i < l && prev != null ; ++ i ) { prev = prev . next ; } } int left = n - l * ( l - 1 ) / 2 ; if ( left > 0 && left % 2 == 0 ) { ListNode node = prev . next ; prev . next = reverse ( node , left ); } return dummy . next ; } private ListNode reverse ( ListNode head , int l ) { ListNode prev = null ; ListNode cur = head ; ListNode tail = cur ; int i = 0 ; while ( cur != null && i < l ) { ListNode t = cur . next ; cur . next = prev ; prev = cur ; cur = t ; ++ i ; } tail . next = cur ; return prev ; } }# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution : def reverseEvenLengthGroups ( self , head : Optional [ ListNode ]) -> Optional [ ListNode ]: 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 n = 0 t = head while t : t = t . next n += 1 dummy = ListNode ( 0 , head ) prev = dummy 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 left = n - l * ( l - 1 ) // 2 if left > 0 and left % 2 == 0 : prev . next = reverse ( prev . next , left ) return dummy . nextVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.