Facebook Pixel

716. Max Stack 🔒

HardStackDesignLinked ListDoubly-Linked ListOrdered Set
Leetcode Link

Problem Description

You need to design a max stack data structure that combines the functionality of a regular stack with the ability to efficiently retrieve and remove the maximum element.

The MaxStack class should support the following operations:

  1. MaxStack(): Initialize an empty stack object.

  2. push(int x): Add element x to the top of the stack.

  3. pop(): Remove and return the element from the top of the stack (most recently added element).

  4. top(): Return the element at the top of the stack without removing it.

  5. peekMax(): Return the maximum element currently in the stack without removing it.

  6. popMax(): Remove and return the maximum element from the stack. If multiple elements have the same maximum value, remove the one that is closest to the top of the stack (the most recently added among the maximum elements).

Performance Requirements:

  • The top() operation must run in O(1) time
  • All other operations (push, pop, peekMax, popMax) must run in O(log n) time

The challenge is to maintain both stack ordering (LIFO - Last In First Out) and efficient access to the maximum element. When popMax() is called, you need to be able to remove an element that might not be at the top of the stack while preserving the relative order of all other elements.

For example, if you push [5, 1, 5] in that order, the stack from bottom to top is [5, 1, 5]. Calling popMax() should remove and return the top 5 (not the bottom one), leaving the stack as [5, 1].

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

Intuition

The key insight is that we need to maintain two different orderings of the same elements simultaneously: the stack order (insertion order) and the value order (for finding maximum). This immediately suggests using two data structures working together.

For a regular stack, we could use a simple array or linked list. However, since popMax() requires removing an element from potentially anywhere in the stack (not just the top), we need a data structure that supports efficient removal from any position. This leads us to consider a doubly-linked list, where any node can be removed in O(1) time if we have a reference to it.

The second challenge is maintaining quick access to the maximum element. A sorted container like a balanced BST or a sorted list would allow us to find the maximum in O(1) or O(log n) time. But here's the clever part: instead of storing values in both structures separately, we can store node references in the sorted structure that point to nodes in our doubly-linked list.

This creates a beautiful synergy:

  • When we push, we create a node in the doubly-linked list and add a reference to that same node in our sorted structure
  • When we pop, we remove the node from the tail of the linked list and also remove its reference from the sorted structure
  • When we popMax(), we get the node reference from the sorted structure, remove it from there, and also unlink it from the doubly-linked list

The sorted structure (using Python's SortedList) maintains nodes sorted by their values, so the maximum is always at the end. When multiple nodes have the same maximum value, the SortedList maintains them in insertion order, ensuring that popMax() naturally removes the most recently added maximum.

This design elegantly solves the problem of maintaining both orderings while allowing efficient operations on both ends - stack operations on the linked list side and max operations through the sorted structure.

Learn more about Stack and Linked List patterns.

Solution Approach

The solution uses a combination of a doubly-linked list and a sorted list to achieve the required time complexities.

Data Structure Design

1. Node Class:

class Node:
    def __init__(self, val=0):
        self.val = val
        self.prev = None
        self.next = None

Each node stores a value and pointers to both previous and next nodes, enabling bidirectional traversal and O(1) removal.

2. DoubleLinkedList Class: The doubly-linked list uses sentinel nodes (dummy head and tail) to simplify edge cases:

  • head and tail are dummy nodes that always exist
  • Actual elements are inserted between head and tail
  • The stack's top is always at tail.prev

Key methods:

  • append(val): Creates a new node and inserts it just before the tail. Returns the node reference for use in the sorted list.
  • remove(node): Unlinks a node from the list by updating its neighbors' pointers (node.prev.next = node.next and node.next.prev = node.prev)
  • pop(): Removes and returns the node at tail.prev (top of stack)
  • peek(): Returns the value at tail.prev without removal

3. MaxStack Class: Combines two structures:

  • self.stk: A DoubleLinkedList maintaining stack order
  • self.sl: A SortedList containing node references, sorted by node values

Operation Implementation

push(x):

  1. Create and append a new node to the doubly-linked list
  2. Add the same node reference to the sorted list
  3. The sorted list maintains nodes in ascending order by their values

pop():

  1. Remove the last node from the doubly-linked list (stack top)
  2. Remove the same node reference from the sorted list
  3. Return the node's value

top(): Simply return tail.prev.val from the doubly-linked list - this is O(1) since we're just accessing a value.

peekMax(): Return the value of the last node in the sorted list (self.sl[-1].val). Since the list is sorted, the maximum is always at the end.

popMax():

  1. Pop the last node from the sorted list (the maximum)
  2. Remove this same node from the doubly-linked list using the static remove method
  3. Return the node's value

Time Complexity Analysis

  • top(): O(1) - Direct access to the tail's previous node
  • push(): O(log n) - Dominated by sorted list insertion
  • pop(): O(log n) - Dominated by sorted list removal
  • peekMax(): O(1) - Direct access to the last element in sorted list
  • popMax(): O(log n) - Sorted list removal is O(log n), doubly-linked list removal is O(1)

The key insight is that by storing node references rather than values in the sorted list, we can maintain both orderings efficiently and perform cross-structure operations seamlessly.

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 a sequence of operations to see how the two data structures work together:

Initial State:

  • Doubly-linked list: [head] ↔ [tail] (empty)
  • Sorted list: [] (empty)

Operation 1: push(3)

  1. Create a new node with value 3
  2. Insert it into the doubly-linked list between head and tail
  3. Add the node reference to the sorted list
  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [tail]
  • Sorted list: [Node(3)] (sorted by value)

Operation 2: push(5)

  1. Create a new node with value 5
  2. Insert it before tail (after Node(3))
  3. Add to sorted list, which maintains sorted order
  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(5)] ↔ [tail]
  • Sorted list: [Node(3), Node(5)] (sorted by value)

Operation 3: push(1)

  1. Create Node(1) and insert before tail
  2. Sorted list inserts it at the beginning due to its value
  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(5)] ↔ [Node(1)] ↔ [tail]
  • Sorted list: [Node(1), Node(3), Node(5)] (sorted by value)
  • Stack order (bottom to top): [3, 5, 1]

Operation 4: peekMax()

  • Look at the last element in sorted list: Node(5)
  • Return its value: 5
  • No changes to either structure

Operation 5: popMax()

  1. Remove last node from sorted list: Node(5)
  2. Use the node reference to unlink it from the doubly-linked list
  3. Return value 5
  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(1)] ↔ [tail]
  • Sorted list: [Node(1), Node(3)]
  • Stack now contains: [3, 1]

Operation 6: top()

  • Access tail.prev which is Node(1)
  • Return value: 1
  • This is O(1) as we're just following a pointer

Operation 7: push(5) again

  • Create new Node(5) and add to both structures

  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(1)] ↔ [Node(5)] ↔ [tail]

  • Sorted list: [Node(1), Node(3), Node(5)]

Operation 8: push(5) once more

  • Another Node(5) is created (different node object, same value)

  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(1)] ↔ [Node(5)₁] ↔ [Node(5)₂] ↔ [tail]

  • Sorted list: [Node(1), Node(3), Node(5)₁, Node(5)₂]

  • Note: The sorted list maintains insertion order for equal values

Operation 9: popMax()

  • Removes Node(5)₂ (the most recently added 5)

  • This demonstrates that when multiple maximums exist, we remove the one closest to the top

  • Doubly-linked list: [head] ↔ [Node(3)] ↔ [Node(1)] ↔ [Node(5)₁] ↔ [tail]

  • Sorted list: [Node(1), Node(3), Node(5)₁]

The beauty of this approach is that both structures share the same node objects. When we remove a node from one structure, we can immediately locate and remove it from the other using the node reference, maintaining consistency between both orderings efficiently.

Solution Implementation

1from typing import Optional
2from sortedcontainers import SortedList
3
4
5class Node:
6    """Node for doubly linked list."""
7  
8    def __init__(self, val: int = 0) -> None:
9        self.val: int = val
10        self.prev: Optional['Node'] = None
11        self.next: Optional['Node'] = None
12
13
14class DoublyLinkedList:
15    """Doubly linked list with sentinel nodes for efficient operations."""
16  
17    def __init__(self) -> None:
18        # Create sentinel head and tail nodes
19        self.head: Node = Node()
20        self.tail: Node = Node()
21        # Connect sentinels
22        self.head.next = self.tail
23        self.tail.prev = self.head
24
25    def append(self, val: int) -> Node:
26        """
27        Append a new node with given value to the end of the list.
28        Returns the newly created node.
29        """
30        node = Node(val)
31        # Insert before tail sentinel
32        node.next = self.tail
33        node.prev = self.tail.prev
34        self.tail.prev = node
35        node.prev.next = node
36        return node
37
38    @staticmethod
39    def remove(node: Node) -> Node:
40        """
41        Remove a node from the list by updating its neighbors' pointers.
42        Returns the removed node.
43        """
44        node.prev.next = node.next
45        node.next.prev = node.prev
46        return node
47
48    def pop(self) -> Node:
49        """
50        Remove and return the last node (before tail sentinel).
51        """
52        return self.remove(self.tail.prev)
53
54    def peek(self) -> int:
55        """
56        Return the value of the last node without removing it.
57        """
58        return self.tail.prev.val
59
60
61class MaxStack:
62    """
63    Stack data structure that supports retrieving the maximum element.
64    Supports O(log n) push, pop, top, peekMax, and popMax operations.
65    """
66  
67    def __init__(self) -> None:
68        # Doubly linked list to maintain stack order
69        self.stack: DoublyLinkedList = DoublyLinkedList()
70        # Sorted list to efficiently track maximum elements
71        # Stores references to nodes, sorted by their values
72        self.sorted_list: SortedList = SortedList(key=lambda x: x.val)
73
74    def push(self, x: int) -> None:
75        """
76        Push element x onto the stack.
77        """
78        # Add node to the end of the stack
79        node = self.stack.append(x)
80        # Add node reference to sorted list for max tracking
81        self.sorted_list.add(node)
82
83    def pop(self) -> int:
84        """
85        Remove and return the top element from the stack.
86        """
87        # Remove from stack
88        node = self.stack.pop()
89        # Remove from sorted list
90        self.sorted_list.remove(node)
91        return node.val
92
93    def top(self) -> int:
94        """
95        Get the top element without removing it.
96        """
97        return self.stack.peek()
98
99    def peekMax(self) -> int:
100        """
101        Get the maximum element in the stack without removing it.
102        """
103        # Last element in sorted list is the maximum
104        return self.sorted_list[-1].val
105
106    def popMax(self) -> int:
107        """
108        Remove and return the maximum element from the stack.
109        """
110        # Get and remove max node from sorted list
111        node = self.sorted_list.pop()
112        # Remove the same node from the stack
113        DoublyLinkedList.remove(node)
114        return node.val
115
116
117# Your MaxStack object will be instantiated and called as such:
118# obj = MaxStack()
119# obj.push(x)
120# param_2 = obj.pop()
121# param_3 = obj.top()
122# param_4 = obj.peekMax()
123# param_5 = obj.popMax()
124
1/**
2 * Node class for doubly linked list implementation
3 */
4class Node {
5    public int val;
6    public Node prev;
7    public Node next;
8
9    public Node() {
10    }
11
12    public Node(int val) {
13        this.val = val;
14    }
15}
16
17/**
18 * Doubly linked list implementation with sentinel nodes
19 * Used as the underlying stack structure
20 */
21class DoubleLinkedList {
22    private final Node head;  // Sentinel head node
23    private final Node tail;  // Sentinel tail node
24
25    public DoubleLinkedList() {
26        head = new Node();
27        tail = new Node();
28        head.next = tail;
29        tail.prev = head;
30    }
31
32    /**
33     * Append a new node with given value to the end of the list (before tail)
34     * @param val Value to append
35     * @return The newly created node
36     */
37    public Node append(int val) {
38        Node node = new Node(val);
39        node.next = tail;
40        node.prev = tail.prev;
41        tail.prev = node;
42        node.prev.next = node;
43        return node;
44    }
45
46    /**
47     * Remove a node from the list
48     * @param node Node to remove
49     * @return The removed node
50     */
51    public static Node remove(Node node) {
52        node.prev.next = node.next;
53        node.next.prev = node.prev;
54        return node;
55    }
56
57    /**
58     * Remove and return the last node (stack pop operation)
59     * @return The removed node
60     */
61    public Node pop() {
62        return remove(tail.prev);
63    }
64
65    /**
66     * Get the value of the last node without removing it (stack peek operation)
67     * @return Value of the last node
68     */
69    public int peek() {
70        return tail.prev.val;
71    }
72}
73
74/**
75 * MaxStack implementation supporting O(log n) max operations
76 * Uses a doubly linked list for stack operations and a TreeMap for tracking maximum values
77 */
78class MaxStack {
79    private DoubleLinkedList stack;                    // Doubly linked list acting as stack
80    private TreeMap<Integer, List<Node>> valueToNodes; // TreeMap mapping values to their nodes
81
82    public MaxStack() {
83        stack = new DoubleLinkedList();
84        valueToNodes = new TreeMap<>();
85    }
86
87    /**
88     * Push element x onto stack
89     * @param x Element to push
90     */
91    public void push(int x) {
92        // Add node to the stack
93        Node node = stack.append(x);
94        // Add node reference to TreeMap for O(log n) max operations
95        valueToNodes.computeIfAbsent(x, k -> new ArrayList<>()).add(node);
96    }
97
98    /**
99     * Remove and return the element on top of the stack
100     * @return The top element
101     */
102    public int pop() {
103        // Remove node from stack
104        Node node = stack.pop();
105        // Remove node reference from TreeMap
106        List<Node> nodes = valueToNodes.get(node.val);
107        int value = nodes.remove(nodes.size() - 1).val;
108        // Clean up empty list from TreeMap
109        if (nodes.isEmpty()) {
110            valueToNodes.remove(node.val);
111        }
112        return value;
113    }
114
115    /**
116     * Get the element on the top of the stack without removing it
117     * @return The top element
118     */
119    public int top() {
120        return stack.peek();
121    }
122
123    /**
124     * Retrieve the maximum element in the stack without removing it
125     * @return The maximum element
126     */
127    public int peekMax() {
128        // TreeMap's lastKey() gives us the maximum value in O(log n)
129        return valueToNodes.lastKey();
130    }
131
132    /**
133     * Remove and return the maximum element in the stack
134     * @return The maximum element
135     */
136    public int popMax() {
137        // Get the maximum value
138        int maxValue = peekMax();
139        // Get list of nodes with this value
140        List<Node> nodes = valueToNodes.get(maxValue);
141        // Remove the most recent node with max value (last in the list)
142        Node node = nodes.remove(nodes.size() - 1);
143        // Clean up empty list from TreeMap
144        if (nodes.isEmpty()) {
145            valueToNodes.remove(maxValue);
146        }
147        // Remove the node from the doubly linked list
148        DoubleLinkedList.remove(node);
149        return maxValue;
150    }
151}
152
153/**
154 * Your MaxStack object will be instantiated and called as such:
155 * MaxStack obj = new MaxStack();
156 * obj.push(x);
157 * int param_2 = obj.pop();
158 * int param_3 = obj.top();
159 * int param_4 = obj.peekMax();
160 * int param_5 = obj.popMax();
161 */
162
1class MaxStack {
2public:
3    MaxStack() {
4        // Constructor - no initialization needed as containers are empty by default
5    }
6
7    void push(int x) {
8        // Add element to the back of the list (acts as stack top)
9        stack.push_back(x);
10      
11        // Store the value and its position in the list in the multimap
12        // multimap automatically sorts by key (value), allowing O(log n) max retrieval
13        valueToPosition.insert({x, --stack.end()});
14    }
15
16    int pop() {
17        // Get iterator to the last element (stack top)
18        auto stackTopIter = --stack.end();
19        int topValue = *stackTopIter;
20      
21        // Find the corresponding entry in multimap
22        // upper_bound returns iterator to first element > topValue
23        // Decrementing gives us the last occurrence of topValue
24        auto mapIter = --valueToPosition.upper_bound(topValue);
25      
26        // Remove from both data structures
27        valueToPosition.erase(mapIter);
28        stack.erase(stackTopIter);
29      
30        return topValue;
31    }
32
33    int top() {
34        // Return the last element without removing it
35        return stack.back();
36    }
37
38    int peekMax() {
39        // rbegin() points to the largest element in the sorted multimap
40        return valueToPosition.rbegin()->first;
41    }
42
43    int popMax() {
44        // Get iterator to the maximum element in multimap (last element)
45        auto maxMapIter = --valueToPosition.end();
46      
47        // Get the list iterator stored in the multimap entry
48        auto stackIter = maxMapIter->second;
49        int maxValue = *stackIter;
50      
51        // Remove from both data structures
52        valueToPosition.erase(maxMapIter);
53        stack.erase(stackIter);
54      
55        return maxValue;
56    }
57
58private:
59    // Multimap maintains elements sorted by value
60    // Key: element value, Value: iterator pointing to element's position in list
61    multimap<int, list<int>::iterator> valueToPosition;
62  
63    // List acts as the stack (using back as top)
64    // List is used instead of vector to maintain iterator validity after erasure
65    list<int> stack;
66};
67
68/**
69 * Your MaxStack object will be instantiated and called as such:
70 * MaxStack* obj = new MaxStack();
71 * obj->push(x);
72 * int param_2 = obj->pop();
73 * int param_3 = obj->top();
74 * int param_4 = obj->peekMax();
75 * int param_5 = obj->popMax();
76 */
77
1// List node for doubly linked list implementation
2class ListNode {
3    val: number;
4    prev: ListNode | null;
5    next: ListNode | null;
6  
7    constructor(val: number) {
8        this.val = val;
9        this.prev = null;
10        this.next = null;
11    }
12}
13
14// Doubly linked list acts as the stack (using tail as top)
15// Using linked list to maintain reference validity after removal
16let head: ListNode | null = null;
17let tail: ListNode | null = null;
18
19// Map maintains elements sorted by value
20// Key: element value, Value: array of nodes with that value
21// Using array to handle duplicate values
22let valueToNodes: Map<number, ListNode[]> = new Map();
23
24// Sorted array of unique values for O(log n) max retrieval
25let sortedValues: number[] = [];
26
27function MaxStack(): void {
28    // Initialize data structures
29    head = null;
30    tail = null;
31    valueToNodes = new Map();
32    sortedValues = [];
33}
34
35function push(x: number): void {
36    // Create new node for the value
37    const newNode = new ListNode(x);
38  
39    // Add node to the end of linked list (acts as stack top)
40    if (!tail) {
41        head = tail = newNode;
42    } else {
43        tail.next = newNode;
44        newNode.prev = tail;
45        tail = newNode;
46    }
47  
48    // Store the node reference in the map
49    if (!valueToNodes.has(x)) {
50        valueToNodes.set(x, []);
51        // Insert value into sorted array maintaining order
52        const insertIndex = binarySearchInsert(sortedValues, x);
53        sortedValues.splice(insertIndex, 0, x);
54    }
55    valueToNodes.get(x)!.push(newNode);
56}
57
58function pop(): number {
59    // Get the last element (stack top)
60    const topNode = tail!;
61    const topValue = topNode.val;
62  
63    // Remove node from linked list
64    if (topNode.prev) {
65        topNode.prev.next = null;
66        tail = topNode.prev;
67    } else {
68        head = tail = null;
69    }
70  
71    // Remove node reference from map
72    const nodes = valueToNodes.get(topValue)!;
73    nodes.pop(); // Remove the last occurrence
74  
75    // Clean up if no more nodes with this value
76    if (nodes.length === 0) {
77        valueToNodes.delete(topValue);
78        const index = sortedValues.indexOf(topValue);
79        sortedValues.splice(index, 1);
80    }
81  
82    return topValue;
83}
84
85function top(): number {
86    // Return the last element without removing it
87    return tail!.val;
88}
89
90function peekMax(): number {
91    // Return the largest element from sorted array
92    return sortedValues[sortedValues.length - 1];
93}
94
95function popMax(): number {
96    // Get the maximum value
97    const maxValue = sortedValues[sortedValues.length - 1];
98  
99    // Get the last node with maximum value
100    const nodes = valueToNodes.get(maxValue)!;
101    const maxNode = nodes[nodes.length - 1];
102  
103    // Remove node from linked list
104    if (maxNode.prev) {
105        maxNode.prev.next = maxNode.next;
106    } else {
107        head = maxNode.next;
108    }
109  
110    if (maxNode.next) {
111        maxNode.next.prev = maxNode.prev;
112    } else {
113        tail = maxNode.prev;
114    }
115  
116    // Remove node reference from map
117    nodes.pop();
118  
119    // Clean up if no more nodes with this value
120    if (nodes.length === 0) {
121        valueToNodes.delete(maxValue);
122        sortedValues.pop();
123    }
124  
125    return maxValue;
126}
127
128// Helper function for binary search insertion
129function binarySearchInsert(arr: number[], target: number): number {
130    let left = 0;
131    let right = arr.length;
132  
133    while (left < right) {
134        const mid = Math.floor((left + right) / 2);
135        if (arr[mid] < target) {
136            left = mid + 1;
137        } else {
138            right = mid;
139        }
140    }
141  
142    return left;
143}
144

Time and Space Complexity

Time Complexity:

  • push(x): O(log n) - Appending to the doubly linked list is O(1), but adding to the SortedList requires O(log n) for binary search insertion
  • pop(): O(n) - Removing from the tail of the doubly linked list is O(1), but removing from the SortedList requires O(n) as it needs to find and shift elements after removal
  • top(): O(1) - Simply returns the value at the tail of the doubly linked list
  • peekMax(): O(1) - Accessing the last element in the SortedList is constant time
  • popMax(): O(n) - Popping from the SortedList is O(n) due to element shifting, and removing from the doubly linked list is O(1)

Space Complexity:

  • O(n) where n is the number of elements in the stack
  • The doubly linked list stores n nodes
  • The SortedList stores references to the same n nodes
  • Each node contains a value and two pointers (prev and next)
  • Total space is O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Storing Values Instead of Node References in SortedList

One of the most common mistakes is storing just the values in the sorted list rather than the actual node references. This breaks the connection between the two data structures and makes it impossible to efficiently remove the correct element during popMax().

Incorrect approach:

class MaxStack:
    def __init__(self):
        self.stack = DoublyLinkedList()
        self.sorted_list = SortedList()  # Stores values, not nodes!
  
    def push(self, x):
        self.stack.append(x)
        self.sorted_list.add(x)  # Wrong: adding value instead of node
  
    def popMax(self):
        max_val = self.sorted_list.pop()
        # Problem: How do we find and remove the RIGHT occurrence 
        # of max_val from the stack? If there are duplicates,
        # we might remove the wrong one!

Why this fails: When multiple elements have the same value, you can't determine which specific node to remove from the doubly-linked list. The problem requires removing the most recently added maximum (closest to top), but with only values, you'd have to traverse the entire stack to find it, breaking the O(log n) time complexity requirement.

Solution: Always store node references in the sorted list, using a key function for sorting:

self.sorted_list = SortedList(key=lambda x: x.val)

2. Forgetting to Handle Bidirectional Updates

Another pitfall is updating only one data structure while forgetting the other, leading to inconsistency.

Incorrect approach:

def pop(self):
    node = self.stack.pop()
    # Forgot to remove from sorted_list!
    return node.val

Why this fails: The sorted list will still contain a reference to a node that's no longer in the stack. Future peekMax() or popMax() operations might try to access this orphaned node, causing incorrect results or errors.

Solution: Always update both data structures in tandem:

def pop(self):
    node = self.stack.pop()
    self.sorted_list.remove(node)  # Don't forget this!
    return node.val

3. Using Built-in List Instead of Doubly-Linked List

Some might try to use Python's built-in list for the stack, which seems simpler but creates problems.

Incorrect approach:

class MaxStack:
    def __init__(self):
        self.stack = []  # Using regular list
        self.sorted_list = SortedList()
  
    def popMax(self):
        max_val = self.sorted_list.pop()
        # Problem: Removing from middle of list is O(n)!
        idx = self.stack.index(max_val)  # O(n) search
        return self.stack.pop(idx)  # O(n) removal

Why this fails: Removing an element from an arbitrary position in a Python list requires O(n) time because all subsequent elements must be shifted. This violates the O(log n) requirement for popMax().

Solution: Use a doubly-linked list where node removal from any position is O(1) once you have the node reference.

4. Not Handling Duplicate Values Correctly

When the stack contains duplicate maximum values, the implementation must remove the correct one (most recently added).

Incorrect approach:

def popMax(self):
    max_val = self.sorted_list[-1].val
    # Wrong: This might remove the first occurrence, not the last
    for node in self.sorted_list:
        if node.val == max_val:
            self.sorted_list.remove(node)
            DoublyLinkedList.remove(node)
            return max_val

Why this fails: This removes the first occurrence of the maximum value encountered in the sorted list, which might not be the most recently added one.

Solution: The sorted list should maintain stable ordering for equal elements (most implementations do this by default), and you should always remove from the end:

def popMax(self):
    node = self.sorted_list.pop()  # Removes the last (most recent) max
    DoublyLinkedList.remove(node)
    return node.val
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

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

Load More