2487. Remove Nodes From Linked List

2487. Remove Nodes From Linked List

6th May 2024 Leetcode daily

ยท

4 min read

Problem Statement

You are given the head of a linked list.

Remove every node which has a node with a greater value anywhere to the right side of it.

Return theheadof the modified linked list.

Examples

Example 1:

Input: head = [5,2,13,3,8]
Output: [13,8]
Explanation: The nodes that should be removed are 5, 2 and 3.
- Node 13 is to the right of node 5.
- Node 13 is to the right of node 2.
- Node 8 is to the right of node 3.

Example 2:

Input: head = [1,1,1,1]
Output: [1,1,1,1]
Explanation: Every node has value 1, so no nodes are removed.

Constraints

  • The number of the nodes in the given list is in the range [1, 10^5].

  • 1 <= Node.val <= 10^5

Intuition

Understanding the problem

We are given head of a singly LinkedList, we have remove every node which has a node with a greater value anywhere to the right side of it.

The problem we have to solve is, how to keep a track of all the nodes which are greater or smaller than the current node. Lets see how we can tackle this problem.

Approach

Using Monotonic Stack

If we think about solving this problem using a data-structure, we can use a monotonic stack. You can read more about Monotonic stacks here:

Read about Monotonic Stack here
A monotonic stack is a specialized stack that maintains a specific order, either non-increasing (elements on top are greater than or equal to those below) or non-decreasing. This allows for efficient solutions to problems where you need to find elements with certain relationships to their neighbors in an array, like finding the next greater element or validating parentheses. While similar to regular stacks with push, pop, top, and isEmpty operations, monotonic stacks enforce the order constraint during push operations, potentially popping elements that violate it.

By using a monotonic stack, we can ensure that while traversing the LinkedList, we will always have values that are greater than the values on their left side. You can see the dry run of the same below:

monotonic stack

At every iteration, we continue pushing values to the stack. When we encounter a greater value, we pop elements until we find a value larger than the current one.

Using Reverse and Filter

We reverse the LinkedList, then we traverse the list, keeping nodes with values greater than or equal to the previous node's value. After finishing the traversal, we reverse the list again to restore the original order.

Solution

Using Monotonic Stack

Python code:

# 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]:
        # initialize the stack
        stack=[]
        # duplicate head to a local variable
        current_node=head
        # loop till end of linked list
        while current_node:
            # if there is value i stack
            # and last value of stack is less than current node's value
            while stack and stack[-1].val<current_node.val:
                # pop from the stack
                # as these nodes have nodes with greater values on their right side
                stack.pop()
            # append next value to stack
            stack.append(current_node)
            # move to next node
            current_node=current_node.next
        # local variable to reconnect nodes in stack
        next_node=None
        # reconnect the nodes in stack
        while stack:
            # get node from stack
            current_node=stack.pop()
            # make current_node's next to next_node's next
            current_node.next=next_node
            # make current_node the next_node
            next_node=current_node
        # return the reconnected list
        return current_node

Using Reverse and Filter

# 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]:
        # check if linkedlist is empty or has only one node
        if not head or not head.next:
            return head
        # Reverse the linkedlist
        prev_node,curr_node=None,head
        while curr_node:
            curr_node.next,prev_node,curr_node=prev_node,curr_node,curr_node.next

        # make curr_node equal to prev_node.next
        # and prev_node.next equal to None
        curr_node, prev_node.next = prev_node.next, None
        # iterate till curr_node is not None
        while curr_node:
            # initialize a temp to remember next of curr_node
            temp = curr_node.next
            # if curr_node's value is greater than prev_node's val
            # tehn remove the node and reverse
            if curr_node.val >= prev_node.val:
                curr_node.next = prev_node
                prev_node = curr_node
            # make curr_node equal to temp(next of curr_node)
            curr_node = temp
        # return prev_node as this is the new head
        return prev_node

Time and Space complexity

Using Monotonic Stack

Time Complexity:

$$O(n)$$

Since we're iterating over the LinkedList at least once.

Space Complexity:

$$O(n)$$

Since we're using a stack and in worst case it will have same size as input linked list.

Using Reverse and Filter

Time Complexity:

$$O(n)$$

Since we're iterating over the LinkedList at least once.

Space Complexity:

$$O(1)$$

As we're not allocating any extra space in this approach

Conclusion

In this problem we get to learn two very good concepts:

  • Using a monotonic stack

  • Using Reverse and Filter

Hope that explanation made sense and you found this blog helpful. If you've made it this far, please give it a like and drop a comment.

Happy Coding!๐Ÿš€

ย