Facebook Pixel

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 because 13 (which is to its right) is greater than 5
  • Node with value 2 should be removed because 13 (which is to its right) is greater than 2
  • Node with value 13 stays because no node to its right is greater than 13
  • Node with value 3 should be removed because 8 (which is to its right) is greater than 3
  • 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.

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

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 than 3 but not 13, pop 3, 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) where n 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 array nums and the stack stk.

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 Evaluator

Example 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:

  1. Process 4:

    • Stack is empty, push 4
    • Stack: [4]
  2. Process 6:

    • Top of stack is 4, which is less than 6
    • Pop 4 (it has a greater value 6 to its right)
    • Stack is now empty, push 6
    • Stack: [6]
  3. Process 3:

    • Top of stack is 6, which is greater than 3
    • No popping needed, push 3
    • Stack: [6, 3]
  4. Process 7:

    • Top of stack is 3, which is less than 7
    • Pop 3 (it has a greater value 7 to its right)
    • Top of stack is now 6, which is less than 7
    • Pop 6 (it has a greater value 7 to its right)
    • Stack is now empty, push 7
    • Stack: [7]
  5. Process 2:

    • Top of stack is 7, which is greater than 2
    • No popping needed, push 2
    • Stack: [7, 2]

Step 3: Reconstruct Linked List Create a new linked list from stack values [7, 2]:

  • Final result: 7 -> 2

Verification:

  • Node 4 was removed (has 6, 7 to its right which are greater)
  • Node 6 was removed (has 7 to its right which is greater)
  • Node 3 was removed (has 7 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 in O(n) operations
  • Reconstructing the linked list from the stack: O(k) where k ≤ 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 most n elements: O(n)
  • The new linked list nodes created from the stack elements: O(k) where k ≤ 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:

  1. When you determine a node should be removed, you need access to its previous node to update the links
  2. Traversing forward makes it difficult to know if a current node should be kept until you've seen all nodes to its right
  3. 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:

  1. Empty list or single node: The algorithm should handle None or a single-node list gracefully
  2. All nodes removed: If the list is strictly increasing (e.g., 1->2->3->4), only the last node remains
  3. 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:

  1. Converting to array first: This eliminates pointer management complexity and allows easy access to all values
  2. Using monotonic stack pattern: This well-established pattern ensures correctness and optimal O(n) complexity
  3. 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.

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

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

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

Load More