Implement a Queue Using Two Stacks
Solve this Problemenqueue(v), dequeue(), front(), isEmpty(), and size(). This version takes a fixed sequence of operations and replays them, returning the result of every dequeue, front, isEmpty, and size call as an array.
This is the mirror image of building a stack from queues: a stack always hands back its newest element, the opposite of what a queue needs. One fix reorders on every enqueue, keeping the oldest value permanently on top so dequeue and front stay cheap. A better fix defers that reordering: let values pile up cheaply, and only pay the reordering cost when something actually needs to come off the front — and crucially, that cost only recurs once the "read-out" side runs dry, not on every single call. Spread across a whole sequence of operations, each element is only ever moved between the two stacks a constant number of times, giving every operation O(1) time on average even though individual dequeues can occasionally do more work than others.
Test Case 1:
Test Case 2:
Test Case 3:
Constraints
- ◆
1 ≤ number of operations ≤ 12 - ◆
Each operation is one of enqueue, dequeue, front, isEmpty, or size - ◆
0 ≤ enqueued value ≤ 1000 - ◆
dequeue and front return -1 when the queue is empty - ◆
Only stack operations (push onto the top, pop from the top, peek the top) may be used to store elements - ◆
This version replays a fixed sequence of operations and reports the result of every dequeue, front, isEmpty, and size call, in order (enqueue has no return value)
Try the Dry Run
Don't just read the solution — watch it execute, one step at a time.
Approach & Solutions
Two Stacks, Enqueue Costly
BruteKeep the queue's contents in stack s1, oldest value always on top, so dequeue and front are trivial O(1) pops/peeks. To enqueue a new value while preserving that ordering: pour every element out of s1 into a helper stack s2 (which naturally reverses them), push the new value onto s2 — putting it underneath everything, since it's the newest and belongs at the bottom — then pour s2 back into s1, restoring the oldest-on-top order with the new value correctly at the very bottom. Every enqueue touches every existing element twice, so it costs O(n).
O(n) per enqueue, O(1) per dequeue/frontO(n)1class Solution {
2 public int[] queueOpsFromStacks(String[] ops, int[] args) {
3 Deque<Integer> s1 = new ArrayDeque<>();
4 Deque<Integer> s2 = new ArrayDeque<>();
5 List<Integer> results = new ArrayList<>();
6 for (int i = 0; i < ops.length; i++) {
7 String op = ops[i];
8 if (op.equals("enqueue")) {
9 while (!s1.isEmpty()) s2.push(s1.pop());
10 s2.push(args[i]);
11 while (!s2.isEmpty()) s1.push(s2.pop());
12 } else if (op.equals("dequeue")) {
13 results.add(s1.isEmpty() ? -1 : s1.pop());
14 } else if (op.equals("front")) {
15 results.add(s1.isEmpty() ? -1 : s1.peek());
16 } else if (op.equals("isEmpty")) {
17 results.add(s1.isEmpty() ? 1 : 0);
18 } else {
19 results.add(s1.size());
20 }
21 }
22 int[] output = new int[results.size()];
23 for (int i = 0; i < results.size(); i++) output[i] = results.get(i);
24 return output;
25 }
26}Optimal — Two Stacks, Amortized O(1)
OptimalInstead of reordering on every enqueue, defer the reordering until it's actually needed. enqueue always just pushes onto s1 — O(1), no matter what. dequeue and front first check a second stack, s2: if it's empty, pour all of s1 into it in one pass (which reverses the order, putting the oldest value on top of s2); then read from s2's top. Crucially, that pour only happens when s2 runs dry — every element gets poured across the two stacks at most once before it's eventually popped, so across any sequence of n operations the total pouring work is O(n), making every operation O(1) on average (amortized), even though a single dequeue can occasionally trigger an O(n) pour.
O(1) per enqueue, O(1) amortized per dequeue/frontO(n)1class Solution {
2 public int[] queueOpsFromStacks(String[] ops, int[] args) {
3 Deque<Integer> s1 = new ArrayDeque<>();
4 Deque<Integer> s2 = new ArrayDeque<>();
5 List<Integer> results = new ArrayList<>();
6 for (int i = 0; i < ops.length; i++) {
7 String op = ops[i];
8 if (op.equals("enqueue")) {
9 s1.push(args[i]);
10 } else if (op.equals("dequeue")) {
11 if (s2.isEmpty()) {
12 while (!s1.isEmpty()) s2.push(s1.pop());
13 }
14 results.add(s2.isEmpty() ? -1 : s2.pop());
15 } else if (op.equals("front")) {
16 if (s2.isEmpty()) {
17 while (!s1.isEmpty()) s2.push(s1.pop());
18 }
19 results.add(s2.isEmpty() ? -1 : s2.peek());
20 } else if (op.equals("isEmpty")) {
21 results.add((s1.isEmpty() && s2.isEmpty()) ? 1 : 0);
22 } else {
23 results.add(s1.size() + s2.size());
24 }
25 }
26 int[] output = new int[results.size()];
27 for (int i = 0; i < results.size(); i++) output[i] = results.get(i);
28 return output;
29 }
30}