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.
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 (initiallyhead
)curr
points to the second node (initiallyhead.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):
- Store the next node in a temporary variable
t = curr.next
- Disconnect the current node by setting
prev.next = t
- Insert the current node at the beginning by setting
curr.next = head
- Update the head pointer to the current node with
head = curr
- Continue traversal from the stored next node with
curr = t
When curr.val >= 0
(non-negative value):
- Simply move both pointers forward:
prev = curr
andcurr = curr.next
- 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 EvaluatorExample 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 1prev
points to node with value 1curr
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, movecurr
to 3
Step 2: Process node with value 3
- Since 3 ≥ 0, keep it in place
- Move
prev
to 3, movecurr
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, movecurr
to 5
Step 4: Process node with value 5
- Since 5 ≥ 0, keep it in place
- Move
prev
to 5, movecurr
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
takesO(1)
- Pointer manipulations (
prev.next = t
,curr.next = head
, etc.) takeO(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 nodecurr
: pointer to track the current nodet
: 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
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
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!