86. Partition List
Problem Description
You are given the head of a linked list and an integer value x
. Your task is to rearrange the linked list so that all nodes with values less than x
appear before all nodes with values greater than or equal to x
.
The key requirements are:
- Nodes with values
< x
should come first - Nodes with values
≥ x
should come after - The relative order within each group must be preserved
For example, if you have a linked list 1 -> 4 -> 3 -> 2 -> 5 -> 2
and x = 3
:
- Nodes less than 3:
1 -> 2 -> 2
(maintaining their original order) - Nodes greater than or equal to 3:
4 -> 3 -> 5
(maintaining their original order) - Result:
1 -> 2 -> 2 -> 4 -> 3 -> 5
The solution creates two separate linked lists:
- One list
l
collects all nodes with values less thanx
- Another list
r
collects all nodes with values greater than or equal tox
As we traverse the original linked list, we append each node to the appropriate list based on its value. Finally, we connect the tail of the "less than" list to the head of the "greater than or equal" list to form the partitioned result.
The code uses dummy head nodes (l
and r
) to simplify the list construction, and maintains tail pointers (tl
and tr
) to efficiently append nodes to each list. The line tr.next = None
ensures the last node doesn't point to any remaining nodes from the original list, and tl.next = r.next
connects the two partitions together.
Intuition
When we need to partition elements based on a value while maintaining relative order, the natural approach is to think about grouping. Imagine you're sorting mail into two bins - one for local addresses and one for out-of-state addresses, but you need to keep the order in which the mail arrived within each bin.
The key insight is that we don't need to modify the original linked list in place, which would be complex and require careful pointer manipulation. Instead, we can build the result by distributing nodes into two separate chains as we traverse the original list.
Think of it like having two queues at a theme park - one for regular visitors and one for VIP pass holders. As people arrive, they join the appropriate queue based on their ticket type, maintaining their arrival order within each queue. At the end, we simply connect the regular queue to the VIP queue.
This leads us to the two-pointer approach:
- Create two separate chains - one for nodes with values
< x
and another for values≥ x
- As we traverse the original list, append each node to the appropriate chain
- Connect the two chains at the end
Using dummy head nodes simplifies the implementation because we don't need special handling for the first node in each chain. The tail pointers (tl
and tr
) allow us to efficiently append nodes in O(1) time without having to traverse to the end of each chain repeatedly.
The elegance of this solution lies in its simplicity - we're essentially doing a stable partition by physically separating the nodes into two groups and then joining them, rather than trying to rearrange pointers within a single list structure.
Learn more about Linked List and Two Pointers patterns.
Solution Approach
The implementation uses a simulation approach with two separate linked lists to partition the nodes. Here's how the algorithm works step by step:
1. Initialize Two Dummy Nodes:
l = ListNode() # Dummy head for nodes < x r = ListNode() # Dummy head for nodes >= x
We create two dummy nodes to serve as the heads of our two partition lists. These dummy nodes simplify list construction by eliminating edge cases for empty lists.
2. Set Up Tail Pointers:
tl, tr = l, r
We maintain tail pointers for both lists. These pointers always point to the last node in each partition, allowing us to append new nodes in O(1) time.
3. Traverse and Distribute Nodes:
while head:
if head.val < x:
tl.next = head
tl = tl.next
else:
tr.next = head
tr = tr.next
head = head.next
For each node in the original list:
- If the node's value is less than
x
, append it to the "less than" list by connecting it totl.next
and advancingtl
- Otherwise, append it to the "greater than or equal" list by connecting it to
tr.next
and advancingtr
- Move to the next node in the original list
4. Clean Up and Connect:
tr.next = None # Terminate the right list
tl.next = r.next # Connect left list to right list
- Set
tr.next = None
to ensure the last node of the "greater than or equal" list doesn't point to any remaining nodes from the original list - Connect the tail of the "less than" list to the first actual node of the "greater than or equal" list (skipping the dummy node with
r.next
)
5. Return the Result:
return l.next
Return l.next
to skip the dummy node and start from the first actual node of the partitioned list.
Time Complexity: O(n) where n is the number of nodes, as we traverse the list once.
Space Complexity: O(1) as we only use a constant amount of extra space for pointers. We're reusing the existing nodes rather than creating new ones.
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 small example to illustrate the solution approach.
Input: Linked list: 3 -> 1 -> 2
, x = 2
Step 1: Initialize dummy nodes and tail pointers
l (dummy) -> null [for nodes < 2]
tl points to l
r (dummy) -> null [for nodes >= 2]
tr points to r
head points to 3
Step 2: Process first node (value = 3)
- Since 3 >= 2, append to right list
tr.next = node(3)
, thentr = node(3)
l (dummy) -> null
tl still points to l
r (dummy) -> 3 -> 1 -> 2
tr now points to node(3)
head moves to node(1)
Step 3: Process second node (value = 1)
- Since 1 < 2, append to left list
tl.next = node(1)
, thentl = node(1)
l (dummy) -> 1 -> 2
tl now points to node(1)
r (dummy) -> 3 -> 1 -> 2
tr still points to node(3)
head moves to node(2)
Step 4: Process third node (value = 2)
- Since 2 >= 2, append to right list
tr.next = node(2)
, thentr = node(2)
l (dummy) -> 1 -> 2
tl still points to node(1)
r (dummy) -> 3 -> 2
tr now points to node(2)
head becomes null (end of list)
Step 5: Clean up and connect
- Set
tr.next = null
to terminate right list - Connect lists:
tl.next = r.next
(connects node(1) to node(3))
l (dummy) -> 1 -> 3 -> 2 -> null
Step 6: Return result
- Return
l.next
which points to node(1)
Final Result: 1 -> 3 -> 2
The partition is complete: node with value 1 (< 2) comes first, followed by nodes with values 3 and 2 (>= 2), maintaining their original relative order.
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
7from typing import Optional
8
9class Solution:
10 def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
11 # Create dummy heads for two partitions
12 # left_dummy: for nodes with values less than x
13 # right_dummy: for nodes with values greater than or equal to x
14 left_dummy = ListNode()
15 right_dummy = ListNode()
16
17 # Maintain pointers to build the two lists
18 left_tail = left_dummy
19 right_tail = right_dummy
20
21 # Traverse the original linked list
22 while head:
23 if head.val < x:
24 # Add current node to the left partition
25 left_tail.next = head
26 left_tail = left_tail.next
27 else:
28 # Add current node to the right partition
29 right_tail.next = head
30 right_tail = right_tail.next
31
32 # Move to the next node
33 head = head.next
34
35 # Terminate the right partition to avoid cycles
36 right_tail.next = None
37
38 # Connect the left partition to the right partition
39 left_tail.next = right_dummy.next
40
41 # Return the head of the partitioned list (skip dummy node)
42 return left_dummy.next
43
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 partition(ListNode head, int x) {
13 // Create dummy heads for two separate lists
14 // leftDummy: for nodes with values less than x
15 // rightDummy: for nodes with values greater than or equal to x
16 ListNode leftDummy = new ListNode();
17 ListNode rightDummy = new ListNode();
18
19 // Maintain pointers to track the tail of each list
20 ListNode leftTail = leftDummy;
21 ListNode rightTail = rightDummy;
22
23 // Traverse the original linked list
24 while (head != null) {
25 if (head.val < x) {
26 // Add current node to the left list (values < x)
27 leftTail.next = head;
28 leftTail = leftTail.next;
29 } else {
30 // Add current node to the right list (values >= x)
31 rightTail.next = head;
32 rightTail = rightTail.next;
33 }
34 // Move to the next node
35 head = head.next;
36 }
37
38 // Terminate the right list to avoid cycles
39 rightTail.next = null;
40
41 // Connect the left list to the right list
42 leftTail.next = rightDummy.next;
43
44 // Return the head of the partitioned list (skip dummy node)
45 return leftDummy.next;
46 }
47}
48
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 */
11class Solution {
12public:
13 ListNode* partition(ListNode* head, int x) {
14 // Create dummy heads for two partitions
15 // lessHead: dummy head for nodes with values < x
16 // greaterHead: dummy head for nodes with values >= x
17 ListNode* lessHead = new ListNode();
18 ListNode* greaterHead = new ListNode();
19
20 // Maintain tail pointers for efficient appending
21 ListNode* lessTail = lessHead;
22 ListNode* greaterTail = greaterHead;
23
24 // Traverse the original list and partition nodes
25 while (head != nullptr) {
26 if (head->val < x) {
27 // Append to the 'less than x' partition
28 lessTail->next = head;
29 lessTail = lessTail->next;
30 } else {
31 // Append to the 'greater or equal to x' partition
32 greaterTail->next = head;
33 greaterTail = greaterTail->next;
34 }
35 head = head->next;
36 }
37
38 // Terminate the greater partition to avoid cycles
39 greaterTail->next = nullptr;
40
41 // Connect the two partitions: less partition -> greater partition
42 lessTail->next = greaterHead->next;
43
44 // Return the head of the partitioned list (skip dummy node)
45 return lessHead->next;
46 }
47};
48
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 * Partitions a linked list around a value x, such that all nodes less than x
15 * come before nodes greater than or equal to x.
16 * The relative order within each partition is preserved.
17 *
18 * @param head - The head of the input linked list
19 * @param x - The partition value
20 * @returns The head of the partitioned linked list
21 */
22function partition(head: ListNode | null, x: number): ListNode | null {
23 // Create dummy heads for two separate lists:
24 // leftDummy: for nodes with values less than x
25 // rightDummy: for nodes with values greater than or equal to x
26 const leftDummy: ListNode = new ListNode();
27 const rightDummy: ListNode = new ListNode();
28
29 // Maintain tail pointers for both lists to append nodes efficiently
30 let leftTail: ListNode = leftDummy;
31 let rightTail: ListNode = rightDummy;
32
33 // Traverse the original linked list
34 let current: ListNode | null = head;
35 while (current !== null) {
36 if (current.val < x) {
37 // Append nodes with values less than x to the left list
38 leftTail.next = current;
39 leftTail = leftTail.next;
40 } else {
41 // Append nodes with values >= x to the right list
42 rightTail.next = current;
43 rightTail = rightTail.next;
44 }
45 current = current.next;
46 }
47
48 // Terminate the right list to avoid cycles
49 rightTail.next = null;
50
51 // Connect the left list to the right list
52 leftTail.next = rightDummy.next;
53
54 // Return the head of the partitioned list (skip dummy node)
55 return leftDummy.next;
56}
57
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the original linked list. The algorithm traverses the entire linked list exactly once in the while loop, performing constant-time operations (comparisons and pointer assignments) for each node.
Space Complexity: O(1)
. Although the code creates two dummy nodes (l
and r
) and uses four pointer variables (tl
, tr
, l
, r
), these occupy a constant amount of extra space regardless of the input size. The algorithm modifies the existing nodes by rearranging their pointers rather than creating new nodes, so no additional space proportional to the input size is required.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Forgetting to Set the Last Node's Next Pointer to None
The Problem:
One of the most critical yet easily overlooked steps is setting right_tail.next = None
after distributing all nodes. Without this line, the last node in the right partition might still point to nodes from the original linked list, creating either:
- A cycle - if the last node points back to an earlier node that's now part of one of the partitions
- Incorrect nodes - if the last node points to nodes that shouldn't be in the final result
Example of the Bug:
Consider the list 1 -> 4 -> 3 -> 2 -> 5 -> 2
with x = 3
:
- After processing, we have:
- Left list:
1 -> 2 -> 2
- Right list:
4 -> 3 -> 5
- Left list:
- The node with value
5
was originally pointing to the second2
node - Without setting
right_tail.next = None
, the result would be:1 -> 2 -> 2 -> 4 -> 3 -> 5 -> 2 -> ...
(potentially creating a cycle)
The Solution: Always terminate the right partition explicitly:
# CRITICAL: Must set this to None to avoid cycles or extra nodes
right_tail.next = None
# Then connect the partitions
left_tail.next = right_dummy.next
Alternative Approach to Avoid This Pitfall: Create new nodes instead of reusing existing ones (though this uses O(n) extra space):
def partition_safe(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
left_dummy = ListNode()
right_dummy = ListNode()
left_tail = left_dummy
right_tail = right_dummy
while head:
# Create new nodes to avoid pointer issues
new_node = ListNode(head.val)
if head.val < x:
left_tail.next = new_node
left_tail = left_tail.next
else:
right_tail.next = new_node
right_tail = right_tail.next
head = head.next
# No need to set right_tail.next = None since new nodes have None by default
left_tail.next = right_dummy.next
return left_dummy.next
Key Takeaway: When manipulating linked list pointers by reusing existing nodes, always be mindful of what the original next pointers are pointing to. Explicitly terminate lists to prevent unintended connections.
Which of these properties could exist for a graph but not a 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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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
Want a Structured Path to Master System Design Too? Don’t Miss This!