Design a queue data structure using stack operations only. A queue follows FIFO order, meaning First In First Out, while a stack follows LIFO order, meaning Last In First Out.
The queue must support adding a value at the back, removing the front value, reading the front value, and checking whether the queue is empty. Stack operations available for the design are push to top, pop from top, read top, size check, and empty check. If pop or peek is performed on an empty queue, -1 must be returned.
Example 1
Input: Operations = push(10), push(20), peek(), push(30), pop(), pop(), peek(), pop(), empty()
Output: 10, 10, 20, 30, 30, true
Explanation: Values 10 and 20 enter before the first front lookup. Value 30 enters after peek(), but values 10 and 20 still leave first. Value 30 becomes the front only after both older values leave.
Example 2
Input: Operations = push(10), pop(), empty()
Output: 10, true
Explanation: The only queued value is removed, so the queue becomes empty.
Brute Force Approach
A queue removes the oldest value first, while a stack removes the newest value first. Keeping the queue front at the top of mainStack solves the difference.
During push(), all existing values move to helperStack. The new value enters mainStack, and all older values move back. The oldest value then remains at the top.
This approach makes pop() and peek() easy, but push() takes O(N) time.
Operation Flow
push(value):
Move all values from
mainStacktohelperStack.Push the new value into
mainStack.Move all values back to
mainStack.
pop():
Return
-1whenmainStackis empty.Remove and return the top value.
peek():
Return
-1whenmainStackis empty.Return the top value without removal.
empty():
Return whether
mainStackis empty.
Algorithm
Two stacks named
mainStackandhelperStackare maintained so the front of the queue stays on top ofmainStack.During
push, all values frommainStackare moved tohelperStackso the new value can be placed below older values.The new value is pushed into
mainStackbecause the value belongs at the back of the queue.All values from
helperStackare moved back tomainStackso the oldest value returns to the top.During
pop, an emptymainStackreturns-1so invalid removal is handled safely.During
peek, an emptymainStackreturns-1so invalid front lookup is handled safely.The
emptyoperation checks onlymainStackbecause all queue values are restored intomainStackafter everypush.
Dry Run
Queue using stack Approach 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: stack<int> mainStack; stack<int> helperStack;public: // Adds a value at the back of the queue. void push(int x) { // Existing values are moved away first. // The new value must sit below older values. while (!mainStack.empty()) { helperStack.push(mainStack.top()); mainStack.pop(); } mainStack.push(x); // Older values are restored above the new value. // The queue front returns to the stack top. while (!helperStack.empty()) { mainStack.push(helperStack.top()); helperStack.pop(); } } // Removes and returns the front value. int pop() { // An empty main stack means no queue value exists. if (mainStack.empty()) { return -1; } int frontValue = mainStack.top(); mainStack.pop(); return frontValue; } // Returns the front value without removal. int peek() { // An empty main stack means no front value exists. if (mainStack.empty()) { return -1; } return mainStack.top(); } // Checks whether the queue has no values. bool empty() { return mainStack.empty(); }};// Driver codeint main() { Solution obj; obj.push(1); obj.push(2); cout << obj.peek() << " "; cout << obj.pop() << " "; cout << boolalpha << obj.empty(); return 0;}Complexity Analysis
Time Complexity: O(N) for push(), because all existing values move to helperStack and then back to mainStack. pop(), peek(), and empty() take O(1) time because the queue front always remains at the top of mainStack.
Space Complexity: O(N), mainStack and helperStack can each require space for up to N values. Counting both stack capacities gives O(N) + O(N) = O(2*N). Removing the constant factor simplifies the total to O(N).
Optimal Approach
Moving all values during every push() is unnecessary. New values stay in inputStack until peek() or pop() needs the queue front.This method uses lazy transfer, meaning values move from inputStack to outputStack only when outputStack becomes empty.
The transfer reverses the order and places the oldest value on top. Each value moves only once, so the average time per operation is O(1).
Operation Flow
push(value):
Push the value into
inputStack.
pop():
Perform lazy transfer when
outputStackis empty.Return
-1when both stacks are empty.Remove and return the top of
outputStack.
peek():
Perform lazy transfer when
outputStackis empty.Return
-1when both stacks are empty.Return the top of
outputStackwithout removal.
empty():
Return true only when both stacks are empty.
Algorithm
Two stacks named
inputStackandoutputStackare maintained so new values can wait separately from values already ready for removal.During
push, the new value is pushed intoinputStackbecause no queue order is needed until a front operation occurs.Before
poporpeek,outputStackis checked because an available top value already represents the queue front and needs no transfer.During
poporpeek, an emptyoutputStacktriggers a full transfer frominputStackso stack reversal brings the oldest waiting value to the top.During
pop, an empty state after transfer returns-1so invalid removal is handled safely.During
peek, an empty state after transfer returns-1so invalid front lookup is handled safely.The
emptyoperation checks both stacks because values may wait in either stack.
Dry Run
queue-using-stacks-lazy-transfer-push-after-peek
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: stack<int> inputStack; stack<int> outputStack; // Moves values only without an outgoing front. void shiftStacks() { // Existing outgoing values already preserve queue order. if (!outputStack.empty()) { return; } // Incoming values are reversed. // The oldest value reaches the outgoing top. while (!inputStack.empty()) { outputStack.push(inputStack.top()); inputStack.pop(); } }public: // Adds a value at the back of the queue. void push(int x) { inputStack.push(x); } // Removes and returns the front value. int pop() { shiftStacks(); // Empty output after shifting means no value exists. if (outputStack.empty()) { return -1; } int frontValue = outputStack.top(); outputStack.pop(); return frontValue; } // Returns the front value without removal. int peek() { shiftStacks(); // Empty output after shifting means no front exists. if (outputStack.empty()) { return -1; } return outputStack.top(); } // Checks whether the queue has no values. bool empty() { return inputStack.empty() && outputStack.empty(); }};// Driver codeint main() { Solution obj; obj.push(10); obj.push(20); cout << obj.peek() << " "; obj.push(30); cout << obj.pop() << " "; cout << obj.pop() << " "; cout << obj.peek() << " "; cout << obj.pop() << " "; cout << boolalpha << obj.empty(); return 0;}Complexity Analysis
Time Complexity: O(N), one pop or peek can take O(N) in the worst case because an empty outputStack can require N pops from inputStack and N pushes into outputStack. The transfer cost is O(N) + O(N) = O(2*N). Removing the constant factor simplifies the transfer to O(N). A pop or peek with values already in outputStack takes O(1). Across a sequence of operations, every value transfers at most once, so total transfer work for N values is O(N) and the average cost per operation is O(1). Both push and empty always take O(1).
Space Complexity: O(N), inputStack and outputStack can each require space for up to N values. Counting both stack capacities gives O(N) + O(N) = O(2*N). Removing the constant factor simplifies the total to O(N).
Interview follow-up Questions
One stack alone cannot expose the oldest value while preserving all newer values with only top-based stack operations. A second stack or the recursion call stack is needed for reversal.
Be the first to add a comment.