Queue Using Two Stacks in DSA Go - Time & Space Complexity
We want to understand how fast operations run when we build a queue using two stacks.
Specifically, how does the time to add or remove items change as the queue grows?
Analyze the time complexity of the following code snippet.
// Enqueue adds item to stack1
func (q *Queue) Enqueue(val int) {
q.stack1 = append(q.stack1, val)
}
// Dequeue removes item from stack2 or transfers from stack1
func (q *Queue) Dequeue() int {
if len(q.stack2) == 0 {
for len(q.stack1) > 0 {
val := q.stack1[len(q.stack1)-1]
q.stack1 = q.stack1[:len(q.stack1)-1]
q.stack2 = append(q.stack2, val)
}
}
val := q.stack2[len(q.stack2)-1]
q.stack2 = q.stack2[:len(q.stack2)-1]
return val
}
This code shows how to add items to one stack and remove them from another to mimic a queue.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Moving all items from stack1 to stack2 when stack2 is empty during dequeue.
- How many times: This transfer happens only when stack2 is empty, not every dequeue.
When adding items, each enqueue is quick and constant time.
When removing items, sometimes we move many items at once, but not every time.
| Input Size (n) | Approx. Operations per Dequeue |
|---|---|
| 10 | About 1 or 10 operations occasionally |
| 100 | About 1 or 100 operations occasionally |
| 1000 | About 1 or 1000 operations occasionally |
Pattern observation: Most dequeues are fast, but some take longer when transferring items.
Time Complexity: O(1) amortized
This means each operation is very fast on average, even if some take longer sometimes.
[X] Wrong: "Every dequeue takes as long as the number of items in the queue because of the transfer."
[OK] Correct: The transfer happens only when stack2 is empty, so most dequeues are quick, making the average time per operation constant.
Understanding this helps you explain how clever use of data structures can keep operations fast on average, a skill valued in problem solving.
"What if we used three stacks instead of two? How would the time complexity change?"
Practice
Queue Using Two Stacks?Solution
Step 1: Understand queue and stack behavior
A queue follows FIFO (first in, first out), while a stack follows LIFO (last in, first out).Step 2: Use two stacks to reverse order
By pushing elements into one stack and popping them into another, the order reverses, simulating queue behavior.Final Answer:
Use one stack for enqueue and another for dequeue to reverse order -> Option BQuick Check:
Two stacks reverse order to mimic queue = B [OK]
- Thinking both stacks store elements in sorted order
- Confusing stack behavior with queue behavior
- Using stacks to separate even and odd elements
x into a stack named stack1 in Go?Solution
Step 1: Understand Go slice push operation
In Go, stacks are often implemented using slices, and pushing means appending an element.Step 2: Use append to add element
The correct way to push is usingstack1 = append(stack1, x)to addxat the end.Final Answer:
stack1 = append(stack1, x) -> Option AQuick Check:
Go slice push uses append = A [OK]
- Using non-existent Push() method on slice
- Using add() or insert() which are not Go slice methods
- Trying to push without reassigning slice
type Queue struct {
stack1 []int
stack2 []int
}
func (q *Queue) Enqueue(x int) {
q.stack1 = append(q.stack1, x)
}
func (q *Queue) Dequeue() int {
if len(q.stack2) == 0 {
for len(q.stack1) > 0 {
n := len(q.stack1) - 1
q.stack2 = append(q.stack2, q.stack1[n])
q.stack1 = q.stack1[:n]
}
}
n := len(q.stack2) - 1
val := q.stack2[n]
q.stack2 = q.stack2[:n]
return val
}
func main() {
q := Queue{}
q.Enqueue(10)
q.Enqueue(20)
q.Enqueue(30)
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
q.Enqueue(40)
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
}Solution
Step 1: Trace enqueue operations
Enqueue 10, 20, 30 adds to stack1: [10, 20, 30]Step 2: Dequeue operations transfer stack1 to stack2 and pop
First dequeue moves all from stack1 to stack2 reversing order: stack2 = [30, 20, 10]. Pop from stack2 returns 10, then 20. Enqueue 40 adds to stack1: [40]. Next dequeue pops 30 from stack2. Last dequeue pops 40 from stack1 after transfer.Final Answer:
10 20 30 40 -> Option DQuick Check:
Queue order preserved by two stacks = A [OK]
- Assuming stack1 pops directly for dequeue
- Not transferring all elements from stack1 to stack2
- Mixing order of elements after transfer
func (q *Queue) Dequeue() int {
if len(q.stack2) == 0 {
for len(q.stack1) > 0 {
n := len(q.stack1) - 1
q.stack2 = append(q.stack2, q.stack1[n])
q.stack1 = q.stack1[:n]
}
}
val := q.stack2[0]
q.stack2 = q.stack2[1:]
return val
}Solution
Step 1: Understand stack2 usage in dequeue
stack2 stores elements in reversed order; popping should remove last element (end of slice).Step 2: Identify incorrect element access
Accessing q.stack2[0] removes first element, breaking LIFO order of stack2 and queue FIFO behavior.Final Answer:
Accessing q.stack2[0] instead of last element causes wrong dequeue order -> Option CQuick Check:
Dequeue must pop last stack2 element = C [OK]
- Removing first element of stack2 instead of last
- Not checking empty stacks before dequeue
- Misusing slice indices in loop
Peek() function for the queue using two stacks in Go that returns the front element without removing it. Which approach correctly returns the front element?Solution
Step 1: Understand peek logic in two-stack queue
Peek must return the front element, which is at the top of stack2 if not empty.Step 2: Transfer elements if stack2 empty and return last element
If stack2 is empty, transfer all from stack1 to stack2 to reverse order, then peek last element of stack2 without removing.Final Answer:
If stack2 is empty, transfer all elements from stack1 to stack2, then return last element of stack2 -> Option AQuick Check:
Peek front = top of stack2 after transfer = D [OK]
- Peeking first element of stack1 which is wrong order
- Always returning last element of stack1 ignoring stack2
- Transferring elements in wrong direction
Ready to prove it?
Pass the test and this pattern moves unknown
