2487. Remove Nodes From Linked List
Problem Description
You are given the head
of a linked list. Your task is to remove every node that has at least one node with a greater value somewhere to its right in the linked list.
In other words, for each node in the linked list, if there exists any node to its right (later in the list) that has a greater value, that node should be removed from the list.
After removing all such nodes, return the head
of the modified linked list.
For example, if you have a linked list 5 -> 2 -> 13 -> 3 -> 8
:
- Node with value
5
should be removed because13
(which is to its right) is greater than5
- Node with value
2
should be removed because13
(which is to its right) is greater than2
- Node with value
13
stays because no node to its right is greater than13
- Node with value
3
should be removed because8
(which is to its right) is greater than3
- Node with value
8
stays because there are no nodes to its right
The resulting linked list would be 13 -> 8
.
The key insight is that a node survives only if there is no greater value to its right in the original linked list.
Intuition
When we need to keep only nodes that don't have any greater value to their right, we can think about this problem from a different angle: which nodes should we keep?
A node should be kept if and only if all nodes to its right have values less than or equal to it. This means we're essentially looking for nodes that form a non-increasing sequence when read from right to left.
If we traverse the linked list from left to right and look at the values, we want to maintain only those values that won't be "overshadowed" by any future larger values. This naturally leads us to think about a monotonic stack.
Consider what happens when we encounter each value:
- If a new value is larger than some previous values, those previous values should be removed because they have a greater value to their right (the current value).
- If a new value is smaller than or equal to all previous kept values, it might survive (unless an even larger value appears later).
This pattern matches exactly with a monotonic decreasing stack: when we see a new value, we pop all smaller values from the stack (they can't be in the final answer), then push the current value. The stack maintains values in decreasing order, and these are exactly the values that survive in the final linked list.
For example, with [5, 2, 13, 3, 8]
:
- Start with
5
: stack =[5]
- See
2
: it's smaller, stack =[5, 2]
- See
13
: it's larger than both, pop them all, stack =[13]
- See
3
: it's smaller, stack =[13, 3]
- See
8
: it's larger than3
but not13
, pop3
, stack =[13, 8]
The final stack [13, 8]
gives us our answer.
Learn more about Stack, Recursion, Linked List and Monotonic Stack patterns.
Solution Approach
The solution implements the monotonic stack approach in two phases:
Phase 1: Convert Linked List to Array
First, we traverse the linked list and store all node values in an array nums
. This allows us to process the values more easily:
nums = []
while head:
nums.append(head.val)
head = head.next
Phase 2: Build Monotonic Decreasing Stack
We iterate through the array nums
and maintain a stack stk
that keeps values in non-increasing order from bottom to top:
stk = [] for v in nums: while stk and stk[-1] < v: stk.pop() stk.append(v)
For each value v
:
- While the stack is not empty and the top element is smaller than
v
, we pop the stack. These popped elements cannot be in the final answer because they have a greater value (v
) to their right. - After removing all smaller elements, we push
v
onto the stack.
This ensures the stack maintains a monotonic decreasing property - each element is greater than or equal to the elements above it.
Phase 3: Reconstruct the Linked List
Finally, we build a new linked list from the values in the stack:
dummy = ListNode()
head = dummy
for v in stk:
head.next = ListNode(v)
head = head.next
return dummy.next
We use a dummy node to simplify the list construction. We iterate through the stack (which now contains only the surviving values in the correct order) and create new nodes for each value.
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the number of nodes. Each node is visited once during the initial traversal, and each value is pushed and popped from the stack at most once. - Space Complexity:
O(n)
for storing the arraynums
and the stackstk
.
The algorithm elegantly solves the problem by recognizing that the nodes we want to keep form a monotonic decreasing sequence, which can be efficiently maintained using a stack data structure.
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 the solution with the linked list: 4 -> 6 -> 3 -> 7 -> 2
Step 1: Convert to Array First, we traverse the linked list and store values in an array:
nums = [4, 6, 3, 7, 2]
Step 2: Build Monotonic Decreasing Stack Now we process each value and maintain a stack with non-increasing values:
-
Process
4
:- Stack is empty, push
4
- Stack:
[4]
- Stack is empty, push
-
Process
6
:- Top of stack is
4
, which is less than6
- Pop
4
(it has a greater value6
to its right) - Stack is now empty, push
6
- Stack:
[6]
- Top of stack is
-
Process
3
:- Top of stack is
6
, which is greater than3
- No popping needed, push
3
- Stack:
[6, 3]
- Top of stack is
-
Process
7
:- Top of stack is
3
, which is less than7
- Pop
3
(it has a greater value7
to its right) - Top of stack is now
6
, which is less than7
- Pop
6
(it has a greater value7
to its right) - Stack is now empty, push
7
- Stack:
[7]
- Top of stack is
-
Process
2
:- Top of stack is
7
, which is greater than2
- No popping needed, push
2
- Stack:
[7, 2]
- Top of stack is
Step 3: Reconstruct Linked List
Create a new linked list from stack values [7, 2]
:
- Final result:
7 -> 2
Verification:
- Node
4
was removed (has6
,7
to its right which are greater) - Node
6
was removed (has7
to its right which is greater) - Node
3
was removed (has7
to its right which is greater) - Node
7
remains (no greater values to its right) - Node
2
remains (no values to its right)
The algorithm correctly identifies that only nodes with values 7
and 2
should remain in the final linked list.
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
7class Solution:
8 def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
9 """
10 Remove nodes from linked list where there exists a node with greater value to its right.
11
12 Args:
13 head: Head of the input linked list
14
15 Returns:
16 Head of the modified linked list with nodes removed
17 """
18 # Extract all values from the linked list into an array
19 values = []
20 current = head
21 while current:
22 values.append(current.val)
23 current = current.next
24
25 # Use monotonic decreasing stack to keep only valid nodes
26 # A node is valid if no greater value exists to its right
27 stack = []
28 for value in values:
29 # Remove all smaller values from stack when encountering a larger value
30 while stack and stack[-1] < value:
31 stack.pop()
32 stack.append(value)
33
34 # Reconstruct the linked list from the remaining values in stack
35 dummy_head = ListNode()
36 current = dummy_head
37 for value in stack:
38 current.next = ListNode(value)
39 current = current.next
40
41 return dummy_head.next
42
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 removeNodes(ListNode head) {
13 // Step 1: Extract all values from the linked list into an array list
14 List<Integer> values = new ArrayList<>();
15 ListNode current = head;
16 while (current != null) {
17 values.add(current.val);
18 current = current.next;
19 }
20
21 // Step 2: Use a monotonic decreasing stack to keep only valid nodes
22 // A node is valid if there's no greater value to its right
23 Deque<Integer> monotonicStack = new ArrayDeque<>();
24 for (int value : values) {
25 // Remove all elements from the stack that are smaller than current value
26 // This ensures we only keep nodes that don't have a greater value to their right
27 while (!monotonicStack.isEmpty() && monotonicStack.peekLast() < value) {
28 monotonicStack.pollLast();
29 }
30 // Add current value to the stack
31 monotonicStack.offerLast(value);
32 }
33
34 // Step 3: Reconstruct the linked list from the remaining values in the stack
35 ListNode dummyHead = new ListNode();
36 ListNode currentNode = dummyHead;
37
38 // Build the result linked list from the values in the stack (maintaining order)
39 while (!monotonicStack.isEmpty()) {
40 currentNode.next = new ListNode(monotonicStack.pollFirst());
41 currentNode = currentNode.next;
42 }
43
44 // Return the head of the new linked list (skip dummy node)
45 return dummyHead.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* removeNodes(ListNode* head) {
14 // Step 1: Extract all values from the linked list into a vector
15 vector<int> values;
16 ListNode* current = head;
17 while (current != nullptr) {
18 values.emplace_back(current->val);
19 current = current->next;
20 }
21
22 // Step 2: Use monotonic stack to keep only nodes that don't have
23 // a greater value to their right
24 vector<int> monotonicStack;
25 for (int value : values) {
26 // Remove all elements from stack that are smaller than current value
27 // This ensures we only keep nodes that don't have a greater value after them
28 while (!monotonicStack.empty() && monotonicStack.back() < value) {
29 monotonicStack.pop_back();
30 }
31 monotonicStack.push_back(value);
32 }
33
34 // Step 3: Reconstruct the linked list from the remaining values
35 ListNode* dummyHead = new ListNode(0); // Dummy node to simplify list construction
36 ListNode* currentNode = dummyHead;
37
38 // Create new nodes for each value in the monotonic stack
39 for (int value : monotonicStack) {
40 currentNode->next = new ListNode(value);
41 currentNode = currentNode->next;
42 }
43
44 // Return the actual head (skip dummy node)
45 return dummyHead->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 * Removes nodes from a linked list where each node has a greater valued node to its right
15 * @param head - The head of the input linked list
16 * @returns The head of the modified linked list with nodes removed
17 */
18function removeNodes(head: ListNode | null): ListNode | null {
19 // Extract all values from the linked list into an array
20 const nodeValues: number[] = [];
21 let currentNode: ListNode | null = head;
22
23 while (currentNode !== null) {
24 nodeValues.push(currentNode.val);
25 currentNode = currentNode.next;
26 }
27
28 // Use a monotonic decreasing stack to keep only nodes
29 // that don't have a greater valued node to their right
30 const monotonicStack: number[] = [];
31
32 for (const value of nodeValues) {
33 // Remove all elements from stack that are less than current value
34 while (monotonicStack.length > 0 && monotonicStack[monotonicStack.length - 1] < value) {
35 monotonicStack.pop();
36 }
37 monotonicStack.push(value);
38 }
39
40 // Reconstruct the linked list from the remaining values in the stack
41 const dummyHead: ListNode = new ListNode();
42 let tailNode: ListNode = dummyHead;
43
44 for (const value of monotonicStack) {
45 tailNode.next = new ListNode(value);
46 tailNode = tailNode.next;
47 }
48
49 return dummyHead.next;
50}
51
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of three main operations:
- First traversal of the linked list to extract values into the
nums
array:O(n)
- Processing each element in
nums
with the monotonic stack: Each element is pushed once and popped at most once, resulting inO(n)
operations - Reconstructing the linked list from the stack:
O(k)
wherek ≤ n
is the size of the final stack
Total time complexity: O(n) + O(n) + O(k) = O(n)
Space Complexity: O(n)
The algorithm uses additional space for:
- The
nums
array storing all node values:O(n)
- The
stk
monotonic stack which can contain at mostn
elements:O(n)
- The new linked list nodes created from the stack elements:
O(k)
wherek ≤ n
Total space complexity: O(n) + O(n) + O(k) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Attempting In-Place Modification Without Proper Pointer Management
A common mistake is trying to modify the linked list in-place while traversing it forward. This approach fails because:
- When you determine a node should be removed, you need access to its previous node to update the links
- Traversing forward makes it difficult to know if a current node should be kept until you've seen all nodes to its right
- Managing multiple pointers while modifying the list structure can lead to lost references or broken links
Incorrect Approach Example:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
current = head
prev = None
while current:
# Check if any node to the right is greater
temp = current.next
should_remove = False
while temp:
if temp.val > current.val:
should_remove = True
break
temp = temp.next
if should_remove:
if prev:
prev.next = current.next
else:
head = current.next
else:
prev = current
current = current.next
return head
This approach has O(n²) time complexity and is error-prone with pointer management.
Pitfall 2: Forgetting Edge Cases
Common edge cases that are often overlooked:
- Empty list or single node: The algorithm should handle
None
or a single-node list gracefully - All nodes removed: If the list is strictly increasing (e.g.,
1->2->3->4
), only the last node remains - No nodes removed: If the list is non-increasing (e.g.,
4->3->2->1
), all nodes remain
Solution to Avoid These Pitfalls
The provided solution elegantly avoids these issues by:
- Converting to array first: This eliminates pointer management complexity and allows easy access to all values
- Using monotonic stack pattern: This well-established pattern ensures correctness and optimal O(n) complexity
- Reconstructing from scratch: Building a new linked list is cleaner than in-place modification and avoids pointer confusion
Alternative Correct Approach - Reverse Processing: If you want to work directly with the linked list without converting to an array, consider processing from right to left:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
# Reverse the linked list first
def reverse(node):
prev = None
while node:
next_node = node.next
node.next = prev
prev = node
node = next_node
return prev
# Reverse the list
head = reverse(head)
# Keep track of maximum seen so far
max_val = float('-inf')
dummy = ListNode()
current = dummy
# Process from right to left (now left to right in reversed list)
while head:
if head.val >= max_val:
max_val = head.val
# Keep this node
temp = head.next
head.next = current.next
current.next = head
head = temp
else:
# Skip this node
head = head.next
return dummy.next
This approach maintains O(n) time complexity while working directly with the linked list structure.
You are given an array of intervals where intervals[i] = [start_i, end_i]
represent the start and end of the ith
interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
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
Want a Structured Path to Master System Design Too? Don’t Miss This!