A quack is a data structure combining properties of both stacks and queues. It can be viewed as a list of elements written left to right such that three operations are possible:
push(x)
: add a new itemx
to the left end of the listpop()
: remove and return the item on the left end of the listpull()
: remove the item on the right end of the list.
Implement a quack using three stacks and O(1)
additional memory, so that the amortized time for any push, pop, or pull operation is O(1)
.
Your solution.
1. stack s1 for push/pop; stack s3 for pull; stack s2 is a copy of s1;
2. instance variable sz to keep track the number of remaining elements.
3. push: push the same element to s1 and s2; pop: first check sz, if sz == 0, clear s1, otherwise do s1.pop(). pull: first check sz, if sz == 0, clear s1; otherwise if there is no element in s3, pop all copy elements in s2 to s3, then do s3.pop().
sz is needed here because pop/pull on s1/s3 are not synchronized with each other. Even if all elements have been either poped or pulled, s1 or s3 may still have elements that have already been popped/pulled. So each time sz becomes 0, we need to clear both s1 and s3.
Runtime/space complexity:
space is O(N) as for each element that is pushed, we store an extra copy in s2.
time: push: O(1); pop: as long as sz is not 0, s1 is never empty. When sz is 0, we need to clear both s1 and s3, which takes O(N) time. This only happens following N pop or pull operations, so pop has amortized O(1) time. pull: similar with pop except that it takes another O(N) time to push copy elements from s2 to s3. It also has amortized O(1) time.
public class Quack { private class EmptyQuackException extends Exception{ String error = "Empty Quack Exception!"; } private int sz; private Stack<Integer> s1, s2, s3; public Quack() { s1 = new Stack<>(); s2 = new Stack<>(); s3 = new Stack<>(); } /* when adding new element on left end, do it to both s1 and s2 */ public void push(int v) { s1.push(v); s2.push(v); sz++; } /* when poping from left end, if there is no element left, throw exception; otherwise, pop on s2 and s1. */ public int pop() throws EmptyQuackException{ if(sz == 0) { s1.clear();
s3.clear(); throw new EmptyQuackException(); } s2.pop(); sz--; return s1.pop(); } /* when popping from right end, if there is no element left, throw exception; otherwise, check if there are elements in s3. If there are, simply pop; If there is none, reverse all elements in s2 to s3, then pop from s3. */ public int pull() throws EmptyQuackException { if(sz == 0) { s1.clear();
s3.clear(); throw new EmptyQuackException(); } else if(s3.size() == 0) { while(s2.size() > 0) { s3.push(s2.pop()); } } sz--; return s3.pop(); } }
Editorial:
push: push to the left stack;
pop: if both left and right stacks are empty, throw exception; otherwise if there is no element in left stack, pop half of the elements from right stack to auxillary stack, then pop the remaining half to left stack, then pop all elements in auxillary back to right stack.
pull: similar with pop.
The key point here is that when left is empty, we need to get the bottom half of the right stack since left and stack are in reversed order. So we expose the bottom half of right by first poping the top half of right to a buffer stack. Then by poping the bottom half from right to left, we also reversed the order of these bottom half elements. Lastly, we need to put the top half back from buffer stack to right stack and keep the order of the top half intact, a property naturally maintained by poping elements to a buffer and poping them back from that buffer!
Runtime/space complexity:
space: O(1), no extra copy
runtime: The re-balancing operation takes time proportional to O(N)
. However, since we have guaranteed N/2 elements on both stacks, there must be no fewer than N/2 pull or pop operations between each re-balance. Therefore, we can say that the amortized time for pop
and pull
are each O(1)
. The running time for each push
operation is O(1)
.
public class BalancedQuack { private class EmptyQuackException extends Exception{ String error = "Empty Quack Exception!"; } private Stack<Integer> left, right, auxillary; public BalancedQuack() { left = new Stack<>(); right = new Stack<>(); auxillary = new Stack<>(); } public void push(int v) { left.push(v); } public int pop() throws EmptyQuackException { if(left.size() == 0 && right.size() == 0) { throw new EmptyQuackException(); } else if(left.size() == 0) { for(int i = 0; i < right.size() / 2; i++) { auxillary.push(right.pop()); } while(right.size() > 0) { left.push(right.pop()); } while(auxillary.size() > 0) { right.push(auxillary.pop()); } } return left.pop(); } public int pull() throws EmptyQuackException { if(left.size() == 0 && right.size() == 0) { throw new EmptyQuackException(); } else if(right.size() == 0) { for(int i = 0; i < left.size() / 2; i++) { auxillary.push(left.pop()); } while(left.size() > 0) { right.push(left.pop()); } while(auxillary.size() > 0) { left.push(auxillary.pop()); } } return right.pop(); } }