Facebook Pixel

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 than x
  • Another list r collects all nodes with values greater than or equal to x

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Create two separate chains - one for nodes with values < x and another for values ≥ x
  2. As we traverse the original list, append each node to the appropriate chain
  3. 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 to tl.next and advancing tl
  • Otherwise, append it to the "greater than or equal" list by connecting it to tr.next and advancing tr
  • 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 Evaluator

Example 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), then tr = 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), then tl = 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), then tr = 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:

  1. A cycle - if the last node points back to an earlier node that's now part of one of the partitions
  2. 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
  • The node with value 5 was originally pointing to the second 2 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More