Dinner Plate Stacks
HARDDescription
You have an infinite number of stacks arranged in a row and numbered (left to right) from 0, each of the stacks has the same maximum capacity.
Implement the DinnerPlates class:
DinnerPlates(int capacity)Initializes the object with the maximum capacity of the stackscapacity.void push(int val)Pushes the given integervalinto the leftmost stack with a size less thancapacity.int pop()Returns the value at the top of the rightmost non-empty stack and removes it from that stack, and returns-1if all the stacks are empty.int popAtStack(int index)Returns the value at the top of the stack with the given indexindexand removes it from that stack or returns-1if the stack with that given index is empty.
Example 1:
Input
["DinnerPlates", "push", "push", "push", "push", "push", "popAtStack", "push", "push", "popAtStack", "popAtStack", "pop", "pop", "pop", "pop", "pop"]
[[2], [1], [2], [3], [4], [5], [0], [20], [21], [0], [2], [], [], [], [], []]
Output
[null, null, null, null, null, null, 2, null, null, 20, 21, 5, 4, 3, 1, -1]
Explanation:
DinnerPlates D = DinnerPlates(2); // Initialize with capacity = 2
D.push(1);
D.push(2);
D.push(3);
D.push(4);
D.push(5); // The stacks are now: 2 4
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // Returns 2. The stacks are now: 4
1 3 5
﹈ ﹈ ﹈
D.push(20); // The stacks are now: 20 4
1 3 5
﹈ ﹈ ﹈
D.push(21); // The stacks are now: 20 4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // Returns 20. The stacks are now: 4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(2); // Returns 21. The stacks are now: 4
1 3 5
﹈ ﹈ ﹈
D.pop() // Returns 5. The stacks are now: 4
1 3
﹈ ﹈
D.pop() // Returns 4. The stacks are now: 1 3
﹈ ﹈
D.pop() // Returns 3. The stacks are now: 1
﹈
D.pop() // Returns 1. There are no stacks.
D.pop() // Returns -1. There are still no stacks.
Constraints:
1 <= capacity <= 2 * 1041 <= val <= 2 * 1040 <= index <= 105- At most
2 * 105calls will be made topush,pop, andpopAtStack.
Approaches
Checkout 2 different approaches to solve Dinner Plate Stacks. Click on different approaches to view the approach and algorithm in detail.
Optimized Approach using TreeMap and TreeSet
To overcome the performance issues of the naive approach, we can use more suitable data structures. A TreeMap can store the stacks, and a TreeSet can track which stacks have available capacity. This allows us to find the target stacks for push and pop operations in logarithmic time instead of linear time.
Algorithm
- Data Structures:
TreeMap<Integer, Deque<Integer>> stacks: A map from a stack's index to the stack itself (using aDeque).TreeMapkeeps indices sorted, allowing efficient retrieval of the rightmost stack.TreeSet<Integer> availableStacks: A sorted set of indices of stacks that are not full. This allows efficient retrieval of the leftmost available stack.int capacity: The maximum capacity of each stack.
- Constructor
DinnerPlates(capacity):- Initialize
capacity, theTreeMap, and theTreeSet.
- Initialize
push(val)Operation:- Check if
availableStacksis empty. If so, it means all existing stacks are full. Determine the index for a new stack (which will bestacks.lastKey() + 1, or 0 if no stacks exist) and add this index toavailableStacks. - Get the smallest index from
availableStacksusingfirst(). This is the leftmost stack with available space. - Get the stack at this index from the
stacksmap (creating it if it doesn't exist) and pushvalonto it. - If the stack's size now equals
capacity, remove its index fromavailableStacks.
- Check if
pop()Operation:- Check if the
stacksmap is empty. If so, return -1. - Find the index of the rightmost non-empty stack using
stacks.lastKey(). - Delegate the actual pop logic to
popAtStack(index)to ensure consistency.
- Check if the
popAtStack(index)Operation:- Check if a stack exists at the given
indexand if it's not empty. If not, return -1. - Note whether the stack was full before the pop operation.
- Pop the value from the stack.
- If the stack becomes empty, remove it entirely from the
stacksmap and also fromavailableStacks. - If the stack was full before popping, it now has space, so add its index back to
availableStacks. - Return the popped value.
- Check if a stack exists at the given
Data Structures
This optimized solution uses two primary data structures to manage the stacks efficiently:
TreeMap<Integer, Deque<Integer>> stacks: This maps a stack's index to the stack itself (anArrayDequeis used as the stack implementation). ATreeMapis chosen because it keeps its keys (the stack indices) in sorted order. This property is crucial for thepop()operation, as we can find the rightmost non-empty stack in O(log N) time by callingstacks.lastKey().TreeSet<Integer> availableStacks: This stores the indices of all stacks that currently have a size less thancapacity. ATreeSetmaintains these indices in sorted order, which is perfect for thepush(val)operation. We can find the leftmost stack with available space in O(log K) time by callingavailableStacks.first(), where K is the number of non-full stacks.
Algorithm Details
-
push(val): Instead of scanning, we directly queryavailableStacks. If it's not empty,first()gives us the index of the leftmost stack with space. We push to it. If it becomes full, we remove its index fromavailableStacks. IfavailableStacksis empty, we know we need a new stack at the end of the current row, so we create one at indexstacks.lastKey() + 1. -
pop(): We find the rightmost non-empty stack by getting the largest key in ourstacksTreeMap(lastKey()). Then, we simply callpopAtStackon that index. This avoids a linear scan. -
popAtStack(index): We pop from the specified stack. The key is to correctly update our data structures. If a pop makes a stack empty, we remove it from thestacksmap. If a pop makes a full stack become not full, we add its index toavailableStacksso it can be used for future pushes.
Code Snippet
import java.util.*;
class DinnerPlates {
private int capacity;
private TreeMap<Integer, Deque<Integer>> stacks;
private TreeSet<Integer> availableStacks;
public DinnerPlates(int capacity) {
this.capacity = capacity;
this.stacks = new TreeMap<>();
this.availableStacks = new TreeSet<>();
}
public void push(int val) {
if (availableStacks.isEmpty()) {
int newIndex = stacks.isEmpty() ? 0 : stacks.lastKey() + 1;
availableStacks.add(newIndex);
}
int indexToPush = availableStacks.first();
stacks.computeIfAbsent(indexToPush, k -> new ArrayDeque<>()).push(val);
if (stacks.get(indexToPush).size() == capacity) {
availableStacks.remove(indexToPush);
}
}
public int pop() {
if (stacks.isEmpty()) {
return -1;
}
int indexToPop = stacks.lastKey();
return popAtStack(indexToPop);
}
public int popAtStack(int index) {
if (!stacks.containsKey(index) || stacks.get(index).isEmpty()) {
return -1;
}
Deque<Integer> stack = stacks.get(index);
boolean wasFull = (stack.size() == capacity);
int val = stack.pop();
if (wasFull) {
availableStacks.add(index);
}
if (stack.isEmpty()) {
stacks.remove(index);
availableStacks.remove(index);
}
return val;
}
}
Complexity Analysis
Pros and Cons
- Highly efficient, with all operations running in logarithmic time.
- Scales well for a large number of operations and stacks, passing all constraints.
- Gracefully handles sparse stacks (gaps created by
popAtStack).
- More complex implementation due to managing multiple coordinated data structures.
- Slightly higher memory overhead due to the
TreeMapandTreeSetstructures compared to a simple list.
Code Solutions
Checking out 3 solutions in different languages for Dinner Plate Stacks. Click on different languages to view the code.
class DinnerPlates { private int capacity ; private List < Deque < Integer >> stacks = new ArrayList <>(); private TreeSet < Integer > notFull = new TreeSet <>(); public DinnerPlates ( int capacity ) { this . capacity = capacity ; } public void push ( int val ) { if ( notFull . isEmpty ()) { stacks . add ( new ArrayDeque <>()); stacks . get ( stacks . size () - 1 ). push ( val ); if ( capacity > 1 ) { notFull . add ( stacks . size () - 1 ); } } else { int index = notFull . first (); stacks . get ( index ). push ( val ); if ( stacks . get ( index ). size () == capacity ) { notFull . pollFirst (); } } } public int pop () { return popAtStack ( stacks . size () - 1 ); } public int popAtStack ( int index ) { if ( index < 0 || index >= stacks . size () || stacks . get ( index ). isEmpty ()) { return - 1 ; } int val = stacks . get ( index ). pop (); if ( index == stacks . size () - 1 && stacks . get ( stacks . size () - 1 ). isEmpty ()) { while (! stacks . isEmpty () && stacks . get ( stacks . size () - 1 ). isEmpty ()) { notFull . remove ( stacks . size () - 1 ); stacks . remove ( stacks . size () - 1 ); } } else { notFull . add ( index ); } return val ; } } /** * Your DinnerPlates object will be instantiated and called as such: * DinnerPlates obj = new DinnerPlates(capacity); * obj.push(val); * int param_2 = obj.pop(); * int param_3 = obj.popAtStack(index); */Video Solution
Watch the video walkthrough for Dinner Plate Stacks
Similar Questions
5 related questions you might find useful
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.