2487. Remove Nodes From Linked List - Explanation
Description
Remove every node that has a strictly greater value somewhere after it in the singly linked list. Return the head of the remaining list, preserving the original order of the surviving nodes. An equal value to the right does not cause removal. Input and output arrays list node values from head to tail.
Example 1:
Input: head = [4,7,3,6,2]
Output: [7,6,2]Explanation: Remove 4 because 7 is later, and remove 3 because 6 is later. The surviving nodes form 7 -> 6 -> 2.
Example 2:
Input: head = [5,5,3,3]
Output: [5,5,3,3]Explanation: No node has a strictly greater value to its right. Equal later values do not count, so every node remains.
Example 3:
Input: head = [1,2,3,4]
Output: [4]Explanation: Each of the first three nodes has a greater value later in the list. Only the final node, containing 4, remains.
Constraints:
- The list contains between
1and100000nodes. 1 <= Node.val <= 100000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Linked List Fundamentals - Understanding singly linked list structure, traversal, and pointer manipulation
- Monotonic Stack - Using a stack that maintains elements in strictly increasing or decreasing order
- Linked List Reversal - Reversing a linked list in-place using iterative pointer swapping
1. Convert To Array
Intuition
A node should be removed if there exists a larger value somewhere to its right. To check this efficiently, we first convert the linked list into an array. Then we traverse the array from right to left, keeping track of the maximum value seen so far. Any node with a value smaller than this running maximum gets removed by adjusting the previous node's pointer.
Algorithm
- Create an array with a dummy node at the start, followed by all nodes from the linked list.
- Initialize
rightMaxito track the maximum node seen from the right. - Traverse the array from right to left:
- If the current node's value is less than
rightMaxi.val, update the previous node'snextpointer to skip it. - Otherwise, update
rightMaxito the current node.
- If the current node's value is less than
- Return
arr[0].nextas the new head.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur, arr = head, [ListNode(0, head)]
while cur:
arr.append(cur)
cur = cur.next
rightMaxi = ListNode(0, None)
for i in range(len(arr) - 1, 0, -1):
if rightMaxi.val > arr[i].val:
arr[i - 1].next = rightMaxi
else:
rightMaxi = arr[i]
return arr[0].nextTime & Space Complexity
- Time complexity:
- Space complexity:
2. Monotonic Decreasing Stack
Intuition
We need to keep only nodes that have no larger value to their right. A monotonic decreasing stack naturally maintains this property. As we traverse the list, we pop any stack elements that are smaller than the current node's value. This ensures the stack always contains values in decreasing order from bottom to top, which are exactly the nodes that should remain.
Algorithm
- Initialize an empty stack to store node values.
- Traverse the linked list:
- While the stack is not empty and the current value is greater than the top of the stack, pop from the stack.
- Push the current value onto the stack.
- Build a new linked list from the stack values in order.
- Return the head of the new list.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
stack = []
cur = head
while cur:
while stack and cur.val > stack[-1]:
stack.pop()
stack.append(cur.val)
cur = cur.next
dummy = ListNode()
cur = dummy
for num in stack:
cur.next = ListNode(num)
cur = cur.next
return dummy.nextTime & Space Complexity
- Time complexity:
- Space complexity:
3. Recursion
Intuition
Recursion lets us process the list from right to left naturally. We first recurse to the end, then on the way back, each node compares itself to whatever remains in the processed suffix. If the next node has a larger value, the current node should be removed, so we return head.next instead. Otherwise, we keep the current node.
Algorithm
- Base case: if
headisnull, returnnull. - Recursively process
head.nextand assign the result back tohead.next. - If
head.nextexists andhead.val < head.next.val, returnhead.next(remove current node). - Otherwise, return
head(keep current node).
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
head.next = self.removeNodes(head.next)
if head.next and head.val < head.next.val:
return head.next
return headTime & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
4. Reverse Twice
Intuition
Finding the maximum to the right is hard when traversing left to right, but finding the maximum to the left is easy. By reversing the list first, the problem transforms: now we just need to keep nodes that are greater than or equal to all previous nodes. We traverse once while tracking the running maximum and remove smaller nodes. Finally, we reverse again to restore the original order.
Algorithm
- Reverse the linked list.
- Traverse the reversed list, tracking the maximum value seen so far (
cur_max):- If
cur.next.val < cur_max, skip it by settingcur.next = cur.next.next. - Otherwise, update
cur_maxand move to the next node.
- If
- Reverse the list again to restore original order.
- Return the new head.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse(head):
prev, cur = None, head
while cur:
tmp = cur.next
cur.next = prev
prev, cur = cur, tmp
return prev
head = reverse(head)
cur = head
cur_max = head.val
while cur and cur.next:
if cur.next.val < cur_max:
cur.next = cur.next.next
else:
cur_max = cur.next.val
cur = cur.next
return reverse(head)Time & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Comparing with Immediate Neighbor Instead of Maximum
The problem requires removing nodes that have any larger value to their right, not just an immediately adjacent larger value. A node should be kept only if it is greater than or equal to all nodes to its right. Using the wrong comparison leads to incorrect removals.
Incorrect Stack Maintenance
When using a monotonic stack, the stack must remain strictly decreasing. Forgetting to pop smaller elements before pushing, or using the wrong comparison operator (e.g., >= instead of >), breaks the invariant and produces wrong results.
Losing the New Head After Reversal
In the reverse-twice approach, both reversals change which node is the head. Failing to capture and return the correct head after the second reversal causes the function to return a pointer into the middle of the list or a stale reference.
Sign in to join the discussion