Design a max stack that supports push, pop, top, peekMax and popMax. push(x) -- Push element x onto stack. pop() -- Remove the element on top of the stack and return it. top() -- Get the element on the top. peekMax() -- Retrieve the maximum element in the stack. popMax() -- Retrieve the maximum element in the stack, and remove it. If you find more than one maximum elements, only remove the top-most one. Example 1: MaxStack stack = new MaxStack(); stack.push(5); stack.push(1); stack.push(5); stack.top(); -> 5 stack.popMax(); -> 5 stack.top(); -> 1 stack.peekMax(); -> 5 stack.pop(); -> 1 stack.top(); -> 5
Approach #2: Double Linked List + TreeMap [Accepted]
Intuition
Using structures like Array or Stack will never let us popMax
quickly. We turn our attention to tree and linked-list structures that have a lower time complexity for removal, with the aim of making popMax
faster than O(N)O(N) time complexity.
Say we have a double linked list as our "stack". This reduces the problem to finding which node to remove, since we can remove nodes in O(1)O(1) time.
We can use a TreeMap mapping values to a list of nodes to answer this question. TreeMap can find the largest value, insert values, and delete values, all in O(\log N)O(logN) time.
Algorithm
Let‘s store the stack as a double linked list dll
, and store a map
from value
to a List
of Node
.
- When we
MaxStack.push(x)
, we add a node to ourdll
, and add or update our entrymap.get(x).add(node)
. - When we
MaxStack.pop()
, we find the valueval = dll.pop()
, and remove the node from ourmap
, deleting the entry if it was the last one. - When we
MaxStack.popMax()
, we use themap
to find the relevant node tounlink
, and return it‘s value.
The above operations are more clear given that we have a working DoubleLinkedList
class. The implementation provided uses head
and tail
sentinels to simplify the relevant DoubleLinkedList
operations.
class MaxStack { TreeMap<Integer, List<Node>> map; DoubleLinkedList dll; public MaxStack() { map = new TreeMap(); dll = new DoubleLinkedList(); } public void push(int x) { Node node = dll.add(x); if(!map.containsKey(x)) map.put(x, new ArrayList<Node>()); map.get(x).add(node); } public int pop() { int val = dll.pop(); List<Node> L = map.get(val); L.remove(L.size() - 1); if (L.isEmpty()) map.remove(val); return val; } public int top() { return dll.peek(); } public int peekMax() { return map.lastKey(); } public int popMax() { int max = peekMax(); List<Node> L = map.get(max); Node node = L.remove(L.size() - 1); dll.unlink(node); if (L.isEmpty()) map.remove(max); return max; } } class DoubleLinkedList { Node head, tail; public DoubleLinkedList() { head = new Node(0); tail = new Node(0); head.next = tail; tail.prev = head; } public Node add(int val) { Node x = new Node(val); x.next = tail; x.prev = tail.prev; tail.prev = tail.prev.next = x; return x; } public int pop() { return unlink(tail.prev).val; } public int peek() { return tail.prev.val; } public Node unlink(Node node) { node.prev.next = node.next; node.next.prev = node.prev; return node; } } class Node { int val; Node prev, next; public Node(int v) {val = v;} }
Complexity Analysis
- Time Complexity: O(\log N)O(logN) for all operations except
peek
which is O(1)O(1), where NN is the number of operations performed. Most operations involvingTreeMap
are O(\log N)O(logN). - Space Complexity: O(N)O(N), the size of the data structures used.