Hone logo
Hone
Problems

Implement a Queue Using Two Stacks

This challenge involves creating a queue data structure using only the fundamental operations of two stacks. Queues follow a First-In, First-Out (FIFO) principle, while stacks operate on a Last-In, First-Out (LIFO) basis. Successfully implementing a queue with two stacks demonstrates a deep understanding of these abstract data types and how to manipulate them.

Problem Description

You are tasked with implementing a Queue class in JavaScript. This class should mimic the behavior of a standard queue, supporting the following operations:

  • enqueue(value): Adds an element to the back of the queue.
  • dequeue(): Removes and returns the element from the front of the queue.
  • peek(): Returns the element at the front of the queue without removing it.
  • isEmpty(): Returns true if the queue is empty, false otherwise.

The critical constraint is that you must implement these operations using only two stacks. You cannot use any built-in queue implementations or other array methods that directly simulate queue behavior (like shift()). You can use basic array operations like push() and pop() to simulate stack behavior.

Key Requirements:

  • The Queue class should be initialized with two internal arrays, each representing a stack.
  • The enqueue operation should efficiently add elements to the conceptual "back" of the queue.
  • The dequeue operation should efficiently remove and return elements from the conceptual "front" of the queue.
  • The peek operation should return the front element without modifying the queue.
  • The isEmpty operation should accurately report the queue's state.

Expected Behavior:

When elements are enqueued and then dequeued, they should appear in the same order they were added (FIFO).

Edge Cases:

  • Dequeuing from an empty queue should return undefined or null.
  • Peeking at an empty queue should return undefined or null.
  • Handling multiple enqueues followed by multiple dequeues.
  • Alternating enqueues and dequeues.

Examples

Example 1:

Input:
const queue = new Queue();
queue.enqueue(10);
queue.enqueue(20);
console.log(queue.dequeue()); // Output: 10
queue.enqueue(30);
console.log(queue.dequeue()); // Output: 20
console.log(queue.peek());    // Output: 30
console.log(queue.dequeue()); // Output: 30
console.log(queue.isEmpty()); // Output: true

Explanation:

  1. enqueue(10): Queue state conceptually: [10]
  2. enqueue(20): Queue state conceptually: [10, 20]
  3. dequeue(): Removes and returns 10. Queue state conceptually: [20]
  4. enqueue(30): Queue state conceptually: [20, 30]
  5. dequeue(): Removes and returns 20. Queue state conceptually: [30]
  6. peek(): Returns 30 without removing. Queue state conceptually: [30]
  7. dequeue(): Removes and returns 30. Queue state conceptually: []
  8. isEmpty(): Returns true as the queue is empty.

Example 2:

Input:
const queue = new Queue();
console.log(queue.isEmpty()); // Output: true
console.log(queue.dequeue()); // Output: undefined
console.log(queue.peek());    // Output: undefined

Explanation: The queue is initialized empty, so isEmpty returns true, and dequeue and peek on an empty queue return undefined.

Constraints

  • You must use exactly two arrays to represent the internal stacks.
  • The enqueue, dequeue, peek, and isEmpty operations should have an average time complexity of O(1). Some operations might involve transferring elements between stacks, leading to amortized O(1) performance for dequeue and peek.
  • Do not use any external libraries or built-in queue data structures.

Notes

Consider how you can use one stack to store incoming elements and the other to prepare elements for dequeueing. The core idea is to reverse the order of elements when necessary. Think about when you need to transfer elements from one stack to the other to maintain the FIFO order.

Think about the efficiency of your dequeue and peek operations. If the stack that holds the front elements is empty, you'll need to transfer elements from the other stack. This transfer operation is key to achieving the amortized O(1) complexity.

Loading editor...
javascript