Reorder List
MedPrompt
You are given the head of a singly linked-list. The list can be represented as:
L0 → L1 → … → Ln - 1 → LnReorder the list to be on the following form:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …You may not modify the values in the list's nodes. Only nodes themselves may be changed.
Example 1:
Input: head = [1,2,3,4]
Output: [1,4,2,3]Example 2:
Input: head = [1,2,3,4,5]
Output: [1,5,2,4,3]
Constraints:
- The number of nodes in the list is in the range
[1, 5 * 104]. 1 <= Node.val <= 1000
Approaches
3 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 ArrayList. By storing all nodes in a list, we can easily access any node by its index. This allows us to use two pointers, one at the beginning and one at the end, to rebuild the linked list in the desired order.
Algorithm
- Create an
ArrayListto store the nodes of the linked list. - Traverse the linked list from the
headand add each node to theArrayList. - Initialize two pointers,
left = 0andright = list.size() - 1. - Iterate while
left < right:- Set the
nextof the node at indexleftto point to the node at indexright. - Increment
left. - If
leftbecomes equal toright, break the loop (for odd length lists). - Set the
nextof the node at indexrightto point to the node at the new indexleft. - Decrement
right.
- Set the
- Set the
nextpointer of the last node in the reordered sequence (at indexleft) tonull.
Walkthrough
First, we iterate through the original linked list from head to tail. During this traversal, we add each node to an ArrayList. After the list is fully stored, we can re-wire the next pointers. We use two integer indices, left starting at 0 and right starting at size - 1. We iterate as long as left < right. In each step, we link the node at left to the node at right (nodes.get(left).next = nodes.get(right)). Then, we increment left. To continue the chain, we link the node at right to the new left node (nodes.get(right).next = nodes.get(left)). Then we decrement right. This process continues until the pointers meet or cross. Finally, the next pointer of the last node in the new sequence (which will be at index left when the loop terminates) must be set to null to signify the end of the list.
import java.util.ArrayList;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 void reorderList(ListNode head) { if (head == null || head.next == null) { return; } // 1. Store all nodes in a list List<ListNode> nodes = new ArrayList<>(); ListNode current = head; while (current != null) { nodes.add(current); current = current.next; } // 2. Re-wire the nodes using two pointers int left = 0, right = nodes.size() - 1; while (left < right) { nodes.get(left).next = nodes.get(right); left++; if (left == right) { break; } nodes.get(right).next = nodes.get(left); right--; } // 3. Set the last node's next to null nodes.get(left).next = null; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Conceptually simple and easy to implement.
Avoids complex pointer manipulation during traversal.
Cons
Requires extra space proportional to the number of nodes, which can be significant for large lists.
Generally not the expected solution in an interview setting where in-place solutions are preferred.
This approach uses recursion to traverse the list to the end and then reorders the nodes as the recursion unwinds. The call stack implicitly keeps track of the nodes from the end of the list, which we can then interleave with nodes from the beginning, tracked by a separate pointer.
Algorithm
- Initialize a class member pointer
lefttoheadand a booleanstoptofalse. - Define a recursive function
recurse(right)that takes the current node as an argument. - The base case for the recursion is
right == null. - In the recursive step, call
recurse(right.next). - After the recursive call returns, check the
stopflag. Iftrue, return immediately. - Check if the reordering is complete. This happens when
leftandrightpointers meet (for odd-length lists) orleft.nextpoints toright(for even-length lists). If so, set thenextof the appropriate node tonull, setstoptotrue, and return. - If not complete, perform the re-wiring:
- Store
left.nextin a temporary variable. - Set
left.nexttoright. - Set
right.nextto the temporary variable. - Advance
leftto its new position (left = temp).
- Store
Walkthrough
The core idea is to use the function call stack to our advantage. We define a recursive function that traverses to the end of the list. When the recursion starts to unwind, we are effectively processing nodes from tail to head. We maintain a global or class-level pointer, left, initialized to the head of the list. This pointer will move from the beginning towards the middle. The recursive function takes the current right node as its parameter. The base case is when right is null. After the recursive call recurse(right.next) returns, we are at node right, and the left pointer is at the corresponding node from the beginning. We can then perform the re-wiring. We need a mechanism to stop the re-wiring process once the left and right pointers meet or cross in the middle of the list. A boolean flag or a direct comparison of left and right pointers can be used for this. Once the middle is reached, we terminate the list by setting the next pointer of the middle/last re-wired node to null and signal all parent recursive calls to stop further processing.
/** * 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 { private ListNode left; private boolean stop; public void reorderList(ListNode head) { if (head == null || head.next == null) { return; } this.left = head; this.stop = false; recurse(head); } private void recurse(ListNode right) { if (right == null) { return; } recurse(right.next); if (this.stop) { return; } if (this.left == right || this.left.next == right) { right.next = null; this.stop = true; return; } ListNode temp = this.left.next; this.left.next = right; right.next = temp; this.left = temp; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Performs the reordering in-place without an explicit auxiliary data structure.
Cons
The space complexity is O(N) due to the recursion call stack, which can lead to a StackOverflowError for very long lists.
The logic can be less intuitive than the iterative approaches.
This is the most efficient and standard approach. It solves the problem in-place with constant extra space. The strategy involves three main steps: finding the middle of the list, reversing the second half of the list, and then merging the first half with the reversed second half.
Algorithm
- Find Middle: Use a slow and a fast pointer to find the end of the first half of the list. The
slowpointer will be the last node of the first half. - Split: The node after
slowis the head of the second half. Setslow.next = nullto break the list into two. - Reverse: Iteratively reverse the second half of the list.
- Merge: Initialize two pointers,
p1to the head of the first half andp2to the head of the reversed second half. - Iterate while
p2is not null:- Store the next nodes:
temp1 = p1.next,temp2 = p2.next. - Link
p1top2:p1.next = p2. - Link
p2top1's original next:p2.next = temp1. - Move pointers forward:
p1 = temp1,p2 = temp2.
- Store the next nodes:
Walkthrough
1. Find the Middle Node: We use the classic "slow and fast pointer" technique. A slow pointer moves one step at a time, while a fast pointer moves two steps. When the fast pointer reaches the end of the list, the slow pointer will be at the middle node (or the end of the first half).
2. Split and Reverse the Second Half: Once the middle is found, we split the list into two halves. The next pointer of the slow node is set to null. The node originally after slow becomes the head of the second half. We then reverse this second half using a standard iterative reversal algorithm (using prev, curr, next pointers).
3. Merge the Two Halves: Now we have two independent lists: the first half starting at head, and the reversed second half. We can merge them by interleaving their nodes. We take one node from the first list, then one from the second, and link them together, advancing the pointers in each list until the second list is exhausted.
/** * 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 void reorderList(ListNode head) { if (head == null || head.next == null) { return; } // 1. Find the middle of the list ListNode slow = head; ListNode fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; } // 2. Reverse the second half of the list ListNode secondHead = slow.next; slow.next = null; // Split the list ListNode prev = null; ListNode curr = secondHead; while (curr != null) { ListNode nextNode = curr.next; curr.next = prev; prev = curr; curr = nextNode; } // 'prev' is now the head of the reversed second half // 3. Merge the two halves ListNode firstHead = head; ListNode reversedSecondHead = prev; while (reversedSecondHead != null) { ListNode temp1 = firstHead.next; ListNode temp2 = reversedSecondHead.next; firstHead.next = reversedSecondHead; reversedSecondHead.next = temp1; firstHead = temp1; reversedSecondHead = temp2; } }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Optimal solution with O(1) space complexity.
It's a robust and common pattern for solving linked list problems.
Cons
The implementation is more complex than the auxiliary space approach, involving multiple distinct steps and careful pointer manipulation.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int val=0, ListNode next=null) { * this.val = val; * this.next = next; * } * } */ public class Solution { public void ReorderList ( ListNode head ) { ListNode slow = head ; ListNode fast = head ; while ( fast . next != null && fast . next . next != null ) { slow = slow . next ; fast = fast . next . next ; } ListNode cur = slow . next ; slow . next = null ; ListNode pre = null ; while ( cur != null ) { ListNode t = cur . next ; cur . next = pre ; pre = cur ; cur = t ; } cur = head ; while ( pre != null ) { ListNode t = pre . next ; pre . next = cur . next ; cur . next = pre ; cur = pre . next ; pre = t ; } } }/** * 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 void reorderList ( ListNode head ) { ListNode fast = head , slow = head ; while ( fast . next != null && fast . next . next != null ) { slow = slow . next ; fast = fast . next . next ; } ListNode cur = slow . next ; slow . next = null ; ListNode pre = null ; while ( cur != null ) { ListNode t = cur . next ; cur . next = pre ; pre = cur ; cur = t ; } cur = head ; while ( pre != null ) { ListNode t = pre . next ; pre . next = cur . next ; cur . next = pre ; cur = pre . next ; pre = t ; } } }/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @return {void} Do not return anything, modify head in-place instead. */ var reorderList = function (head) { let slow = head; let fast = head; while (fast.next && fast.next.next) { slow = slow.next; fast = fast.next.next; } let cur = slow.next; slow.next = null; let pre = null; while (cur) { const t = cur.next; cur.next = pre; pre = cur; cur = t; } cur = head; while (pre) { const t = pre.next; pre.next = cur.next; cur.next = pre; cur = pre.next; pre = t; } };/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: void reorderList ( ListNode * head ) { ListNode * fast = head ; ListNode * slow = head ; while ( fast -> next && fast -> next -> next ) { slow = slow -> next ; fast = fast -> next -> next ; } ListNode * cur = slow -> next ; slow -> next = nullptr ; ListNode * pre = nullptr ; while ( cur ) { ListNode * t = cur -> next ; cur -> next = pre ; pre = cur ; cur = t ; } cur = head ; while ( pre ) { ListNode * t = pre -> next ; pre -> next = cur -> next ; cur -> next = pre ; cur = pre -> next ; pre = t ; } } };# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution : def reorderList ( self , head : Optional [ ListNode ]) -> None : fast = slow = head while fast . next and fast . next . next : slow = slow . next fast = fast . next . next cur = slow . next slow . next = None pre = None while cur : t = cur . next cur . next = pre pre , cur = cur , t cur = head while pre : t = pre . next pre . next = cur . next cur . next = pre cur , pre = pre . next , tVideo 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.