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.
rightMaxi to track the maximum node seen from the right.rightMaxi.val, update the previous node's next pointer to skip it.rightMaxi to the current node.arr[0].next as 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].nextWe 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.
# 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.nextRecursion 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.
head is null, return null.head.next and assign the result back to head.next.head.next exists and head.val < head.next.val, return head.next (remove current node).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 headFinding 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.
cur_max):cur.next.val < cur_max, skip it by setting cur.next = cur.next.next.cur_max and move to the next 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]:
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)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.
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.
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.