Splitting a linked list means cutting one list into two or more lists while preserving the original nodes.
Splitting a linked list means cutting one list into two or more lists while preserving the original nodes. The operation changes links, not values. It appears in merge sort, partitioning, alternating lists, palindrome checks, and algorithms that process the front and back halves separately.
Problem
Given the head of a singly linked list, divide it into parts according to a rule.
Common rules include:
split into first half and second half
split after k nodes
split by predicate
split into alternating nodesThe main danger is losing a suffix by overwriting a next pointer before saving it.
Split After k Nodes
To split after exactly k nodes, walk to the kth node and cut the edge after it.
if head == null:
return (null, null)
cur = head
for i in 1..k-1:
if cur.next == null:
return (head, null)
cur = cur.next
second = cur.next
cur.next = null
return (head, second)For:
a -> b -> c -> d -> e -> nullwith k = 3, the result is:
first: a -> b -> c -> null
second: d -> e -> nullThe invariant is that cur always points to the last node of the first part constructed so far. The final assignment cur.next = null makes the first part a complete list.
Split into Two Halves
Use the first-middle convention when the first half should have the same length as the second half, or one extra node when the length is odd.
if head == null or head.next == null:
return (head, null)
slow = head
fast = head.next
while fast != null and fast.next != null:
slow = slow.next
fast = fast.next.next
second = slow.next
slow.next = null
return (head, second)For an even-length list:
a -> b -> c -> d -> nullthe result is:
first: a -> b -> null
second: c -> d -> nullFor an odd-length list:
a -> b -> c -> d -> e -> nullthe result is:
first: a -> b -> c -> null
second: d -> e -> nullThis form is useful for merge sort because it guarantees progress: both halves are smaller than the original list when the length is at least two.
Split by Predicate
Sometimes each node should be placed into one of two output lists depending on its value.
yes_dummy = new Node()
no_dummy = new Node()
yes_tail = yes_dummy
no_tail = no_dummy
cur = head
while cur != null:
next = cur.next
cur.next = null
if predicate(cur.value):
yes_tail.next = cur
yes_tail = cur
else:
no_tail.next = cur
no_tail = cur
cur = next
return (yes_dummy.next, no_dummy.next)The assignment cur.next = null is important. It detaches the current node from the original list before appending it to a new list. Without this step, one output list may accidentally retain a chain of nodes that belong to the other output list.
Stable Partition
A predicate split is stable if nodes retain their original relative order inside each output list. The previous algorithm is stable because it appends nodes to the tail of each output list.
Example:
3 -> 1 -> 4 -> 2 -> 5 -> nullSplit by value < 3:
yes: 1 -> 2 -> null
no: 3 -> 4 -> 5 -> nullThe nodes in each list appear in the same order as in the input.
Alternating Split
An alternating split sends nodes at positions 1, 3, 5, … to the first list and nodes at positions 2, 4, 6, … to the second list.
a_dummy = new Node()
b_dummy = new Node()
a_tail = a_dummy
b_tail = b_dummy
cur = head
take_a = true
while cur != null:
next = cur.next
cur.next = null
if take_a:
a_tail.next = cur
a_tail = cur
else:
b_tail.next = cur
b_tail = cur
take_a = not take_a
cur = next
return (a_dummy.next, b_dummy.next)For:
a -> b -> c -> d -> e -> nullthe result is:
first: a -> c -> e -> null
second: b -> d -> nullThis pattern is useful when distributing work across two passes or when implementing some recursive list algorithms.
Split Around a Pivot
Partition a list into nodes smaller than a pivot and nodes greater than or equal to a pivot.
small_dummy = new Node()
large_dummy = new Node()
small_tail = small_dummy
large_tail = large_dummy
cur = head
while cur != null:
next = cur.next
cur.next = null
if cur.value < pivot:
small_tail.next = cur
small_tail = cur
else:
large_tail.next = cur
large_tail = cur
cur = next
small_tail.next = large_dummy.next
return small_dummy.nextThis is the linked-list analogue of array partitioning, but it avoids swaps. Nodes are moved by relinking.
Splitting for Merge Sort
Merge sort on linked lists begins by splitting the list into halves.
sort(head):
if head == null or head.next == null:
return head
left, right = split_half(head)
left = sort(left)
right = sort(right)
return merge(left, right)The split must physically cut the list. If slow.next = null is omitted, recursive calls may continue seeing the original full list, causing infinite recursion.
Complexity Summary
| Split pattern | Time | Space |
|---|---|---|
| Split after k nodes | O(k) | O(1) |
| Split into halves | O(n) | O(1) |
| Split by predicate | O(n) | O(1) |
| Alternating split | O(n) | O(1) |
| Split around pivot | O(n) | O(1) |
Common Failure Modes
The most common error is overwriting a link before saving the suffix. In any loop that moves nodes into another list, write:
next = cur.next
cur.next = nullbefore appending cur.
Another error is failing to cut the first list when splitting into halves. If second = slow.next is saved but slow.next = null is omitted, both results still share structure.
A third error is returning the sentinel node instead of the first real node. The correct return value is usually dummy.next.
Key Insight
Splitting is controlled separation. The algorithm must preserve reachability while deciding where each node belongs. A safe split follows three steps: save the suffix, detach the current edge, and attach the node or segment to its new owner.