Facebook Pixel

2046. Sort Linked List Already Sorted Using Absolute Values 🔒

Problem Description

You are given the head of a singly linked list where the nodes are sorted in non-decreasing order based on their absolute values. Your task is to rearrange the linked list so that it becomes sorted in non-decreasing order based on the actual values of the nodes.

For example, if you have a linked list with values [-5, -3, 1, 4, 6] (sorted by absolute values: |−5|=5, |−3|=3, |1|=1, |4|=4, |6|=6 which gives us 1, 3, 4, 5, 6), you need to rearrange it to [-5, -3, 1, 4, 6] which is sorted by actual values.

The key insight is that when a list is sorted by absolute values, all negative numbers appear in reverse order compared to how they should appear when sorted by actual values. The solution leverages this property by moving negative nodes to the front of the list using head insertion. Starting from the second node, whenever a negative value is encountered, it's removed from its current position and inserted at the beginning of the list. Non-negative values remain in their current positions since they're already correctly ordered relative to each other.

The algorithm maintains two pointers: prev (the node before the current one) and curr (the current node being examined). When curr.val < 0, the node is extracted from its position and moved to become the new head. When curr.val >= 0, the traversal simply continues to the next node.

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

Intuition

When a linked list is sorted by absolute values, an interesting pattern emerges. Consider the values [-5, -3, -1, 2, 4, 6]. When sorted by absolute values, they become [-1, 2, -3, 4, -5, 6]. Notice that all positive numbers maintain their relative order and are already in the correct positions for the final sorted list. However, the negative numbers appear in reverse order compared to where they should be.

This observation is key: in a list sorted by absolute values, negative numbers with larger absolute values come later in the list, but in a list sorted by actual values, they should come first. For instance, -5 has the largest absolute value among negatives, so it appears last among the negative numbers when sorted by absolute value, but it should be first when sorted by actual value.

The breakthrough comes from realizing we can fix this in a single pass. As we traverse the list from left to right, every negative number we encounter needs to be moved to the front of the list, and more specifically, it needs to be placed before all previously encountered negative numbers. This is exactly what head insertion accomplishes - each newly found negative number becomes the new head, naturally creating the correct ordering.

Why does this work? Because we're processing negative numbers from smallest absolute value to largest absolute value (due to the original sorting), and inserting each at the head means the final order will be from largest absolute value to smallest - which corresponds to smallest actual value to largest for negative numbers. Meanwhile, positive numbers stay in place since they're already correctly ordered, resulting in a fully sorted list by actual values.

Learn more about Linked List, Two Pointers and Sorting patterns.

Solution Approach

The implementation uses the head insertion method with two pointers to rearrange the linked list in a single pass.

We start by initializing two pointers:

  • prev points to the first node (initially head)
  • curr points to the second node (initially head.next)

The algorithm then iterates through the list starting from the second node. For each node, we check if its value is negative:

When curr.val < 0 (negative value found):

  1. Store the next node in a temporary variable t = curr.next
  2. Disconnect the current node by setting prev.next = t
  3. Insert the current node at the beginning by setting curr.next = head
  4. Update the head pointer to the current node with head = curr
  5. Continue traversal from the stored next node with curr = t

When curr.val >= 0 (non-negative value):

  1. Simply move both pointers forward: prev = curr and curr = curr.next
  2. The node remains in its current position

The key insight is that prev doesn't move forward when we extract a negative node - it stays pointing to the same node because we've removed the node between prev and the next node. This ensures we don't skip any nodes during traversal.

For example, with input [0, 2, -1, 4, -3]:

  • Start: prev at 0, curr at 2 (positive, move forward)
  • Next: prev at 2, curr at -1 (negative, extract -1 and insert at head)
  • List becomes [-1, 0, 2, 4, -3], prev stays at 2, curr moves to 4
  • Next: prev at 2, curr at 4 (positive, move forward)
  • Next: prev at 4, curr at -3 (negative, extract -3 and insert at head)
  • Final list: [-3, -1, 0, 2, 4]

The algorithm completes in O(n) time with O(1) extra space, making it an efficient solution.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with a concrete example to see how it transforms a list sorted by absolute values into one sorted by actual values.

Input: [1, -2, 3, -4, 5] (sorted by absolute values: |1|=1, |-2|=2, |3|=3, |-4|=4, |5|=5)

Initial Setup:

  • head points to node with value 1
  • prev points to node with value 1
  • curr points to node with value -2

Step 1: Process node with value -2

  • Since -2 < 0, we need to move it to the front
  • Store next node (3) in temporary variable t
  • Disconnect -2 by setting prev.next = t (1 now points to 3)
  • Insert -2 at head: -2 → 1 → 3 → -4 → 5
  • Update head to point to -2
  • Keep prev at 1, move curr to 3

Step 2: Process node with value 3

  • Since 3 ≥ 0, keep it in place
  • Move prev to 3, move curr to -4
  • List remains: -2 → 1 → 3 → -4 → 5

Step 3: Process node with value -4

  • Since -4 < 0, we need to move it to the front
  • Store next node (5) in temporary variable t
  • Disconnect -4 by setting prev.next = t (3 now points to 5)
  • Insert -4 at head: -4 → -2 → 1 → 3 → 5
  • Update head to point to -4
  • Keep prev at 3, move curr to 5

Step 4: Process node with value 5

  • Since 5 ≥ 0, keep it in place
  • Move prev to 5, move curr to null
  • List remains: -4 → -2 → 1 → 3 → 5

Final Result: [-4, -2, 1, 3, 5] - correctly sorted by actual values!

Notice how negative numbers (-2 and -4) were moved to the front in reverse order of when we encountered them, which naturally sorts them correctly. The positive numbers (1, 3, 5) stayed in their original relative positions since they were already correctly ordered.

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 sortLinkedList(self, head: Optional[ListNode]) -> Optional[ListNode]:
11        # Handle edge case: empty list or single node
12        if not head or not head.next:
13            return head
14      
15        # Initialize pointers: prev points to current node, curr points to next node
16        prev = head
17        curr = head.next
18      
19        # Traverse the linked list
20        while curr:
21            # If current node has negative value, move it to the front
22            if curr.val < 0:
23                # Store the next node temporarily
24                temp_next = curr.next
25              
26                # Remove curr from its current position
27                prev.next = temp_next
28              
29                # Move curr to the front of the list
30                curr.next = head
31                head = curr
32              
33                # Move curr pointer to the next node to process
34                curr = temp_next
35                # Note: prev stays the same since we removed curr
36            else:
37                # If current node is non-negative, simply move forward
38                prev = curr
39                curr = curr.next
40      
41        # Return the new head of the modified list
42        return head
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    /**
13     * Sorts a linked list by moving all negative values to the front
14     * while maintaining the relative order within positive and negative groups.
15     * 
16     * @param head The head of the linked list to be sorted
17     * @return The new head of the sorted linked list
18     */
19    public ListNode sortLinkedList(ListNode head) {
20        // Handle edge case: empty list
21        if (head == null || head.next == null) {
22            return head;
23        }
24      
25        // Initialize pointers for traversal
26        ListNode previous = head;
27        ListNode current = head.next;
28      
29        // Traverse the linked list
30        while (current != null) {
31            // Check if current node contains a negative value
32            if (current.val < 0) {
33                // Store the next node before modifying pointers
34                ListNode nextNode = current.next;
35              
36                // Remove current node from its position
37                previous.next = nextNode;
38              
39                // Move current node to the front of the list
40                current.next = head;
41                head = current;
42              
43                // Move to the next node (previous stays the same)
44                current = nextNode;
45            } else {
46                // If current value is non-negative, simply advance both pointers
47                previous = current;
48                current = current.next;
49            }
50        }
51      
52        return head;
53    }
54}
55
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    /**
14     * Sorts a linked list by moving all negative values to the front
15     * while maintaining the relative order within positive and negative groups
16     * @param head - pointer to the head of the linked list
17     * @return pointer to the new head of the sorted linked list
18     */
19    ListNode* sortLinkedList(ListNode* head) {
20        // Handle edge case: empty list or single node
21        if (!head || !head->next) {
22            return head;
23        }
24      
25        // Initialize pointers for traversal
26        ListNode* prev = head;      // Tracks the node before current
27        ListNode* curr = head->next; // Current node being examined
28      
29        // Traverse the linked list
30        while (curr) {
31            // If current node has a negative value, move it to the front
32            if (curr->val < 0) {
33                // Save the next node before modifying pointers
34                ListNode* nextNode = curr->next;
35              
36                // Remove current node from its position
37                prev->next = nextNode;
38              
39                // Insert current node at the beginning
40                curr->next = head;
41                head = curr;
42              
43                // Move to the next node (prev stays the same)
44                curr = nextNode;
45            } else {
46                // If current node is non-negative, continue traversal
47                prev = curr;
48                curr = curr->next;
49            }
50        }
51      
52        return head;
53    }
54};
55
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 * Sorts a linked list by moving all negative values to the front
15 * while maintaining the relative order within positive and negative groups
16 * @param head - The head node of the linked list
17 * @returns The new head of the sorted linked list
18 */
19function sortLinkedList(head: ListNode | null): ListNode | null {
20    // Handle edge case: empty list
21    if (head === null || head.next === null) {
22        return head;
23    }
24  
25    // Initialize two pointers for traversal
26    let previousNode: ListNode = head;
27    let currentNode: ListNode | null = head.next;
28  
29    // Traverse the linked list
30    while (currentNode !== null) {
31        // Check if current node contains a negative value
32        if (currentNode.val < 0) {
33            // Store the next node temporarily
34            const nextNode: ListNode | null = currentNode.next;
35          
36            // Remove current node from its position
37            previousNode.next = nextNode;
38          
39            // Move current node to the front of the list
40            currentNode.next = head;
41            head = currentNode;
42          
43            // Continue with the next node
44            currentNode = nextNode;
45        } else {
46            // Move both pointers forward if value is non-negative
47            previousNode = currentNode;
48            currentNode = currentNode.next;
49        }
50    }
51  
52    return head;
53}
54

Time and Space Complexity

Time Complexity: O(n), where n is the length of the linked list.

The algorithm traverses the linked list exactly once using a single while loop. In each iteration, it performs constant-time operations:

  • Checking if curr.val < 0 takes O(1)
  • Pointer manipulations (prev.next = t, curr.next = head, etc.) take O(1)
  • Moving to the next node takes O(1)

Since we visit each node exactly once and perform O(1) operations per node, the total time complexity is O(n).

Space Complexity: O(1)

The algorithm uses only a fixed number of variables regardless of the input size:

  • prev: pointer to track the previous node
  • curr: pointer to track the current node
  • t: temporary pointer used during rearrangement

No additional data structures (arrays, stacks, recursion) are used that would grow with the input size. All operations are performed in-place by manipulating the existing linked list pointers. Therefore, the space complexity is constant, O(1).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Forgetting to Handle Edge Cases

Issue: Not checking for empty lists or single-node lists can cause null pointer exceptions when trying to access head.next.

Incorrect Code:

def sortLinkedList(self, head: Optional[ListNode]) -> Optional[ListNode]:
    prev = head
    curr = head.next  # This fails if head is None!
    # ... rest of the code

Solution: Always validate input before processing:

if not head or not head.next:
    return head

Pitfall 2: Incorrectly Updating the prev Pointer

Issue: A common mistake is advancing prev after extracting a negative node, which causes nodes to be skipped.

Incorrect Code:

if curr.val < 0:
    temp_next = curr.next
    prev.next = temp_next
    curr.next = head
    head = curr
    prev = prev.next  # WRONG! This skips checking the next node
    curr = temp_next

Solution: Keep prev pointing to the same node after extraction since the next node now directly follows prev:

if curr.val < 0:
    temp_next = curr.next
    prev.next = temp_next
    curr.next = head
    head = curr
    # Don't move prev!
    curr = temp_next

Pitfall 3: Losing Track of the Next Node

Issue: Failing to store curr.next before modifying pointers results in losing the rest of the list.

Incorrect Code:

if curr.val < 0:
    prev.next = curr.next  # curr.next is about to be modified!
    curr.next = head       # Now the original curr.next is lost
    head = curr
    curr = ???  # We don't have access to the original next node anymore

Solution: Always store the next node in a temporary variable first:

if curr.val < 0:
    temp_next = curr.next  # Store it first!
    prev.next = temp_next
    curr.next = head
    head = curr
    curr = temp_next

Pitfall 4: Not Updating the Head Reference

Issue: Forgetting to update the head reference after inserting a node at the beginning means the newly inserted nodes won't be part of the returned list.

Incorrect Code:

if curr.val < 0:
    temp_next = curr.next
    prev.next = temp_next
    curr.next = head
    # Forgot to update head = curr
    curr = temp_next

Solution: Always update the head pointer after insertion:

if curr.val < 0:
    temp_next = curr.next
    prev.next = temp_next
    curr.next = head
    head = curr  # Critical: update the head!
    curr = temp_next
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).


Recommended Readings

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

Load More