143. Reorder List - Explanation
Description
You are given the head of a singly linked-list.
The positions of a linked list of length = 7 for example, can intially be represented as:
[0, 1, 2, 3, 4, 5, 6]
Reorder the nodes of the linked list to be in the following order:
[0, 6, 1, 5, 2, 4, 3]
Notice that in the general case for a list of length = n the nodes are reordered to be in the following order:
[0, n-1, 1, n-2, 2, n-3, ...]
You may not modify the values in the list's nodes, but instead you must reorder the nodes themselves.
Example 1:
Input: head = [2,4,6,8]
Output: [2,8,4,6]Example 2:
Input: head = [2,4,6,8,10]
Output: [2,10,4,8,6]Constraints:
1 <= Length of the list <= 1000.1 <= Node.val <= 1000
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n) time and O(1) space, where n is the length of the given list.
Hint 1
A brute force solution would be to store the node values of the list in an array, reorder the values, and create a new list. Can you think of a better way? Perhaps you can try reordering the nodes directly in place, avoiding the use of extra space.
Hint 2
For example, consider the list [1, 2, 3, 4, 5]. To reorder the list, we connect the first and last nodes, then continue with the second and second-to-last nodes, and so on. Essentially, the list is split into two halves: the first half remains as is, and the second half is reversed and merged with the first half. For instance, [1, 2] will merge with the reversed [5, 4, 3]. Can you figure out a way to implement this reordering process? Maybe dividing the list into two halves could help.
Hint 3
We can divide the list into two halves using the fast and slow pointer approach, which helps identify the midpoint of the list. This allows us to split the list into two halves, with the heads labeled as l1 and l2. Next, we reverse the second half (l2). After these steps, we proceed to reorder the two lists by iterating through them node by node, updating the next pointers accordingly.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Linked List Fundamentals - Understanding singly linked list structure and pointer manipulation
- Fast and Slow Pointers - Using two pointers moving at different speeds to find the middle of a list
- Linked List Reversal - Reversing a linked list in-place using iterative pointer swapping
- Merging Linked Lists - Interleaving nodes from two separate lists into one
1. Brute Force
Intuition
To reorder the linked list in the pattern:
L0 → Ln → L1 → L(n−1) → L2 → ...
A straightforward approach is to store all nodes in an array.
Once stored, we can easily access nodes from both the start and end using two pointers.
By alternately linking nodes from the front (index i) and back (index j), we can reshape the list in the required order.
Algorithm
- Traverse the linked list and push every node into an array called
nodes. - Initialize two pointers:
i = 0(start)j = len(nodes) - 1(end)
- While
i < j:- Link
nodes[i].nexttonodes[j]; incrementi. - If
i >= j, break the loop. - Link
nodes[j].nexttonodes[i]; decrementj.
- Link
- After the loop, set
nodes[i].next = Noneto terminate the list. - The reordered list is constructed in-place.
# 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:
if not head:
return
nodes = []
cur = head
while cur:
nodes.append(cur)
cur = cur.next
i, j = 0, len(nodes) - 1
while i < j:
nodes[i].next = nodes[j]
i += 1
if i >= j:
break
nodes[j].next = nodes[i]
j -= 1
nodes[i].next = NoneTime & Space Complexity
- Time complexity:
- Space complexity:
2. Recursion
Intuition
This recursive approach reorders the list by pairing nodes from the front and back during the recursion unwind phase.
The idea is:
- Move to the end of the list using recursion.
- On the way back up (unwinding), connect the current node from the end (
cur) to the corresponding node from the front (root). - Advance the front pointer (
root) step-by-step as recursion unwinds. - Stop when the pointers meet or cross.
Recursion naturally processes the list from back to front, making it convenient to match front and back nodes without using extra lists.
Algorithm
- Define a recursive function
rec(root, cur):curmoves to the end of the list via recursion.rootmarks the current front node during unwinding.
- In the base case:
- If
curisNone, return the front pointer (root).
- If
- Recursively call
reconcur.nextto reach the tail. - During unwinding:
- If
rootmeets or crossescur, setcur.next = Noneto finish and stop further links. - Otherwise:
- Temporarily save
root.nextintmp. - Link
root.next → cur. - Link
cur.next → tmp.
- Temporarily save
- If
- Return the next node (
tmp) as the updated front pointer. - Start recursion with
rec(head, head.next).
This reorders the list in place without extra storage.
# 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:
def rec(root: ListNode, cur: ListNode) -> ListNode:
if not cur:
return root
root = rec(root, cur.next)
if not root:
return None
tmp = None
if root == cur or root.next == cur:
cur.next = None
else:
tmp = root.next
root.next = cur
cur.next = tmp
return tmp
head = rec(head, head.next)Time & Space Complexity
- Time complexity:
- Space complexity:
3. Reverse And Merge
Intuition
To reorder the list into the pattern
L1 → Ln → L2 → Ln−1 → L3 → Ln−2 → ...,
we can break the problem into three simple steps:
Find the middle of the linked list using
slowandfastpointers.
This splits the list into two halves.Reverse the second half of the list.
Doing this makes it easy to merge nodes from the front and back alternately.Merge the two halves one-by-one:
Take one node from the first half (first), then one from the reversed second half (second), and repeat.
This method is clean, intuitive, and uses only O(1) extra space.
Algorithm
Find the middle:
- Use
slowandfastpointers. - When
fastreaches the end,slowwill be at the midpoint.
- Use
Reverse the second half:
- Start from
slow.next. - Reverse it using the standard linked-list reversal approach with
prevandtmpvariables.
- Start from
Merge the two lists:
- Take a node from
firsthalf. - Take a node from the reversed
secondhalf. - Continue until
secondis exhausted.
- Take a node from
This produces the desired reordered list in-place with no extra memory.
# 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:
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
second = slow.next
prev = slow.next = None
while second:
tmp = second.next
second.next = prev
prev = second
second = tmp
first, second = head, prev
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first, second = tmp1, tmp2Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Incorrectly Finding the Middle Node
The slow/fast pointer technique must be initialized correctly. Starting both pointers at head versus starting fast at head.next affects whether slow ends at the last node of the first half or the first node of the second half. This off-by-one error leads to incorrect splitting and malformed results.
Forgetting to Disconnect the Two Halves
After finding the middle, you must set slow.next = None to split the list into two independent halves. Without this, the reversal step creates a cycle or corrupts the original list structure, causing infinite loops during merging.
Losing References During Merge
When interleaving nodes from the first and second halves, each next pointer reassignment breaks the original chain. Failing to save tmp1 = first.next and tmp2 = second.next before modifying pointers causes you to lose track of remaining nodes, resulting in an incomplete or broken list.
Sign in to join the discussion