Day 3: Stacks & Queues
What You'll Learn Today
- How stacks (LIFO) work and how to implement them
- How queues (FIFO) work and how to implement them
- The concept of double-ended queues (Deque)
- Practical applications of stacks and queues
Stacks
A stack is a LIFO (Last In, First Out) data structure. The last element added is the first one removed.
flowchart TB
subgraph Stack["Stack"]
direction TB
TOP["β Top"]
E3["30"]
E2["20"]
E1["10"]
end
PUSH["push(30)"] --> TOP
TOP --> POP["pop() β 30"]
style Stack fill:#3b82f6,color:#fff
style PUSH fill:#22c55e,color:#fff
style POP fill:#ef4444,color:#fff
Stack Implementation
class Stack {
constructor() {
this.items = [];
}
push(value) {
this.items.push(value);
}
pop() {
if (this.isEmpty()) return undefined;
return this.items.pop();
}
peek() {
if (this.isEmpty()) return undefined;
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
get size() {
return this.items.length;
}
}
const stack = new Stack();
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack.pop()); // 30
console.log(stack.peek()); // 20
Stack Time Complexities
| Operation | Complexity |
|---|---|
| push | O(1) |
| pop | O(1) |
| peek | O(1) |
| isEmpty | O(1) |
| search | O(n) |
Stack Applications
1. Balanced Parentheses Check
function isValidParentheses(s) {
const stack = [];
const pairs = { ')': '(', ']': '[', '}': '{' };
for (const char of s) {
if ('([{'.includes(char)) {
stack.push(char);
} else if (')]}'.includes(char)) {
if (stack.pop() !== pairs[char]) return false;
}
}
return stack.length === 0;
}
console.log(isValidParentheses("({[]})")); // true
console.log(isValidParentheses("({[)]}")); // false
2. Reverse Polish Notation Evaluation
function evalRPN(tokens) {
const stack = [];
for (const token of tokens) {
if (['+', '-', '*', '/'].includes(token)) {
const b = stack.pop();
const a = stack.pop();
switch (token) {
case '+': stack.push(a + b); break;
case '-': stack.push(a - b); break;
case '*': stack.push(a * b); break;
case '/': stack.push(Math.trunc(a / b)); break;
}
} else {
stack.push(Number(token));
}
}
return stack.pop();
}
console.log(evalRPN(["3", "4", "+"])); // 7
console.log(evalRPN(["2", "3", "+", "4", "*"])); // 20
3. Browser Back/Forward Navigation
class BrowserHistory {
constructor(homepage) {
this.backStack = [];
this.forwardStack = [];
this.current = homepage;
}
visit(url) {
this.backStack.push(this.current);
this.current = url;
this.forwardStack = [];
}
back() {
if (this.backStack.length === 0) return this.current;
this.forwardStack.push(this.current);
this.current = this.backStack.pop();
return this.current;
}
forward() {
if (this.forwardStack.length === 0) return this.current;
this.backStack.push(this.current);
this.current = this.forwardStack.pop();
return this.current;
}
}
Queues
A queue is a FIFO (First In, First Out) data structure. The first element added is the first one removed.
flowchart LR
ENQ["enqueue(30)"] --> BACK["Back"]
subgraph Queue["Queue"]
direction LR
E1["10"]
E2["20"]
E3["30"]
end
FRONT["Front"] --> DEQ["dequeue() β 10"]
style Queue fill:#8b5cf6,color:#fff
style ENQ fill:#22c55e,color:#fff
style DEQ fill:#ef4444,color:#fff
Queue Implementation (Linked List)
Using shift() on an array is O(n), so a linked list implementation is more efficient.
class QueueNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
class Queue {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
enqueue(value) {
const node = new QueueNode(value);
if (this.tail) {
this.tail.next = node;
} else {
this.head = node;
}
this.tail = node;
this.length++;
}
dequeue() {
if (!this.head) return undefined;
const value = this.head.value;
this.head = this.head.next;
if (!this.head) this.tail = null;
this.length--;
return value;
}
peek() {
return this.head ? this.head.value : undefined;
}
isEmpty() {
return this.length === 0;
}
get size() {
return this.length;
}
}
const queue = new Queue();
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
console.log(queue.dequeue()); // 10
console.log(queue.peek()); // 20
Queue Time Complexities
| Operation | Complexity |
|---|---|
| enqueue | O(1) |
| dequeue | O(1) |
| peek | O(1) |
| isEmpty | O(1) |
| search | O(n) |
Queue Applications
1. Task Scheduler
class TaskScheduler {
constructor() {
this.queue = new Queue();
}
addTask(task) {
this.queue.enqueue(task);
console.log(`Task added: ${task.name}`);
}
processNext() {
if (this.queue.isEmpty()) {
console.log("No tasks to process");
return;
}
const task = this.queue.dequeue();
console.log(`Processing: ${task.name}`);
task.execute();
}
processAll() {
while (!this.queue.isEmpty()) {
this.processNext();
}
}
}
2. Breadth-First Search (BFS)
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
const result = [];
visited.add(start);
while (queue.length > 0) {
const node = queue.shift();
result.push(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
return result;
}
const graph = {
A: ['B', 'C'],
B: ['A', 'D'],
C: ['A', 'E'],
D: ['B'],
E: ['C'],
};
console.log(bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E']
Double-Ended Queue (Deque)
A Deque allows adding and removing elements from both ends.
flowchart LR
AFL["addFront()"] --> FRONT["Front"]
subgraph Deque["Deque"]
direction LR
E1["10"]
E2["20"]
E3["30"]
end
BACK["Back"] --> ABL["addBack()"]
FRONT --> RFL["removeFront()"]
BACK --> RBL["removeBack()"]
style Deque fill:#f59e0b,color:#fff
class Deque {
constructor() {
this.items = {};
this.frontIndex = 0;
this.backIndex = 0;
}
addFront(value) {
this.frontIndex--;
this.items[this.frontIndex] = value;
}
addBack(value) {
this.items[this.backIndex] = value;
this.backIndex++;
}
removeFront() {
if (this.isEmpty()) return undefined;
const value = this.items[this.frontIndex];
delete this.items[this.frontIndex];
this.frontIndex++;
return value;
}
removeBack() {
if (this.isEmpty()) return undefined;
this.backIndex--;
const value = this.items[this.backIndex];
delete this.items[this.backIndex];
return value;
}
peekFront() {
return this.items[this.frontIndex];
}
peekBack() {
return this.items[this.backIndex - 1];
}
isEmpty() {
return this.backIndex - this.frontIndex === 0;
}
get size() {
return this.backIndex - this.frontIndex;
}
}
Stack vs. Queue vs. Deque
| Feature | Stack | Queue | Deque |
|---|---|---|---|
| Order | LIFO | FIFO | Both ends |
| Add | top | back | front/back |
| Remove | top | front | front/back |
| Use cases | Function calls, Undo | Task processing, BFS | Sliding window |
Summary
| Concept | Description |
|---|---|
| Stack | LIFO structure with push/pop operations |
| Queue | FIFO structure with enqueue/dequeue operations |
| Deque | General-purpose structure with operations from both ends |
| Call Stack | Uses a stack to manage function invocations |
Key Takeaways
- Both stacks and queues provide O(1) for their primary operations
- JavaScript arrays work well as stacks but poorly as queues (shift is O(n))
- Use a linked list for an efficient queue implementation
- BFS uses queues; DFS uses stacks
Practice Problems
Problem 1: Basic
Implement a queue using two stacks.
class QueueUsingStacks {
constructor() {
this.stack1 = [];
this.stack2 = [];
}
enqueue(value) { /* implement */ }
dequeue() { /* implement */ }
}
Problem 2: Intermediate
Implement a stack that supports a min() method in O(1).
Challenge
Implement a function that finds the maximum value in a sliding window using a Deque in O(n).
References
Next up: In Day 4, we'll explore "Hash Tables" β how to achieve O(1) lookups.