Stack and Queue | Essential Data Structures for Coding Interviews

Stack and Queue | Essential Data Structures for Coding Interviews

이 글의 핵심

Practical guide to stacks and queues. Complete coverage of essential data structures for coding interviews with detailed examples.

Introduction

”When do I use stacks and queues?”

Stacks and queues are used when processing data in specific order. They appear very frequently in coding interviews.

What this article covers:

  • Stack (LIFO)
  • Queue (FIFO)
  • Implementation methods
  • Real-world problem solving

1. Stack

What is a Stack?

A stack is a LIFO (Last In First Out) data structure. The last item inserted comes out first.

Real-life analogies:

  • Stacking plates: Take the top plate first
  • Browser back button: Navigate to most recent page
  • Function call stack: Most recently called function exits first
Stack operations:

    push(3)        push(2)        push(1)
    ↓              ↓              ↓
                            [1]  ← top
                   [2]      [2]
[3]                [3]      [3]  ← bottom

    pop() → 1      pop() → 2      pop() → 3
    ↓              ↓              ↓
[2]  ← top         [3]  ← top     [] (empty)
[3]

Python Implementation (3 Methods)

# Method 1: Using list (simplest, recommended)
stack = []

# push: add element
stack.append(1)  # [1]
stack.append(2)  # [1, 2]
stack.append(3)  # [1, 2, 3]
print(stack)  # [1, 2, 3] (3 is top)

# pop: remove and return top
top = stack.pop()
print(top)  # 3
print(stack)  # [1, 2] (2 is new top)

# top: check without removing
if stack:
    print(stack[-1])  # 2 (last element = top)

# empty check
is_empty = len(stack) == 0
print(is_empty)  # False

# Method 2: collections.deque (more efficient)
from collections import deque

stack = deque()
stack.append(1)
stack.append(2)
stack.append(3)
print(stack.pop())  # 3

# Method 3: Class implementation (explicit, educational)
class Stack:
    def __init__(self):
        self.items = []
    
    def push(self, item):
        self.items.append(item)
    
    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        raise IndexError("pop from empty stack")
    
    def top(self):
        if not self.is_empty():
            return self.items[-1]
        raise IndexError("top from empty stack")
    
    def is_empty(self):
        return len(self.items) == 0
    
    def size(self):
        return len(self.items)

# Usage
s = Stack()
s.push(10)
s.push(20)
print(s.top())   # 20
print(s.pop())   # 20
print(s.size())  # 1

C++ Implementation

#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;
    
    // push: add element
    s.push(1);
    s.push(2);
    s.push(3);
    
    // top: check top (no removal)
    cout << s.top() << endl;  // 3
    
    // pop: remove top (no return!)
    s.pop();  // Remove 3
    
    cout << s.top() << endl;  // 2
    
    // empty: check if empty
    cout << s.empty() << endl;  // 0 (false)
    
    // size: get size
    cout << s.size() << endl;  // 2
    
    return 0;
}

Python vs C++ Difference:

  • Python: pop() returns the value
  • C++: pop() doesn’t return, use top() then pop()

Stack Time Complexity

OperationDescriptionTimePythonC++
push(x)Add elementO(1)append(x)push(x)
pop()Remove topO(1)pop()pop()
top()Check topO(1)[-1]top()
empty()Check emptyO(1)len() == 0empty()
size()Get sizeO(1)len()size()

2. Queue

What is a Queue?

A queue is a FIFO (First In First Out) data structure. The first item inserted comes out first.

Real-life analogies:

  • Standing in line: First person gets served first
  • Printer queue: First document sent prints first
  • Call center: First caller gets answered first
Queue operations:

    enqueue(1)     enqueue(2)     enqueue(3)
    ↓              ↓              ↓
[1]                [1][2]         [1][2][3]
 ↑                  ↑              ↑     ↑
front              front          front  rear

    dequeue() → 1  dequeue() → 2  dequeue() → 3
    ↓              ↓              ↓
[2][3]             [3]            [] (empty)
 ↑                  ↑
front              front

Python Implementation

# Method 1: collections.deque (recommended)
from collections import deque

queue = deque()

# enqueue: add to rear
queue.append(1)  # deque([1])
queue.append(2)  # deque([1, 2])
queue.append(3)  # deque([1, 2, 3])

# dequeue: remove from front
front = queue.popleft()  # O(1) - efficient!
print(front)  # 1
print(queue)  # deque([2, 3])

# front: check without removing
if queue:
    print(queue[0])  # 2

# empty check
is_empty = len(queue) == 0

# ❌ Method 2: list (inefficient, NOT recommended)
queue_list = []
queue_list.append(1)
queue_list.pop(0)  # O(n)! Very slow

deque vs list Performance:

import time
from collections import deque

# deque (O(1))
q = deque(range(100000))
start = time.time()
for _ in range(10000):
    q.popleft()
print(f"deque: {time.time() - start:.4f}s")  # ~0.001s

# list (O(n))
q = list(range(100000))
start = time.time()
for _ in range(10000):
    q.pop(0)
print(f"list: {time.time() - start:.4f}s")  # ~5s (5000x slower!)

C++ Implementation

#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<int> q;
    
    // push: add to rear
    q.push(1);
    q.push(2);
    q.push(3);
    
    // front: check front (no removal)
    cout << q.front() << endl;  // 1
    
    // back: check rear
    cout << q.back() << endl;  // 3
    
    // pop: remove from front (no return!)
    q.pop();  // Remove 1
    
    cout << q.front() << endl;  // 2
    
    // empty: check if empty
    cout << q.empty() << endl;  // 0 (false)
    
    // size: get size
    cout << q.size() << endl;  // 2
    
    return 0;
}

3. Problem Solving

Problem 1: Valid Parentheses

def is_valid_parentheses(s):
    """
    Check if parentheses are valid
    "(())" → True
    "(()" → False
    """
    stack = []
    pairs = {'(': ')', '{': '}', '[': ']'}
    
    for char in s:
        if char in pairs:  # Opening bracket
            stack.append(char)
        else:  # Closing bracket
            if not stack:
                return False
            if pairs[stack.pop()] != char:
                return False
    
    return len(stack) == 0

# Test
print(is_valid_parentheses("()"))        # True
print(is_valid_parentheses("()[]{}"))    # True
print(is_valid_parentheses("(]"))        # False
print(is_valid_parentheses("([)]"))      # False
print(is_valid_parentheses("{[]}"))      # True

C++ Solution:

#include <iostream>
#include <stack>
#include <string>
#include <unordered_map>
using namespace std;

bool isValid(string s) {
    stack<char> st;
    unordered_map<char, char> pairs = {
        {'(', ')'}, {'{', '}'}, {'[', ']'}
    };
    
    for (char c : s) {
        if (pairs.count(c)) {  // Opening bracket
            st.push(c);
        } else {  // Closing bracket
            if (st.empty()) return false;
            
            char opening = st.top();
            st.pop();
            
            if (pairs[opening] != c) return false;
        }
    }
    
    return st.empty();
}

Problem 2: Implement Queue using Stacks

Problem: Implement a queue using two stacks.

Idea:

  • stack_in: for enqueue
  • stack_out: for dequeue
  • When dequeue and stack_out is empty, transfer all from stack_in
class QueueUsingStacks:
    """
    Implement queue using two stacks
    """
    def __init__(self):
        self.stack_in = []   # for enqueue
        self.stack_out = []  # for dequeue
    
    def enqueue(self, x):
        """Add to rear - O(1)"""
        self.stack_in.append(x)
    
    def dequeue(self):
        """Remove from front - Amortized O(1)"""
        if not self.stack_out:
            while self.stack_in:
                self.stack_out.append(self.stack_in.pop())
        
        return self.stack_out.pop() if self.stack_out else None
    
    def front(self):
        """Check front - Amortized O(1)"""
        if not self.stack_out:
            while self.stack_in:
                self.stack_out.append(self.stack_in.pop())
        
        return self.stack_out[-1] if self.stack_out else None
    
    def is_empty(self):
        return not self.stack_in and not self.stack_out

# Test
q = QueueUsingStacks()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.dequeue())  # 1
print(q.front())    # 2
print(q.dequeue())  # 2

Problem 3: Next Greater Element

Problem: Find the next greater element to the right for each element.

Example:

  • [4, 5, 2, 10][5, 10, 10, -1]
  • [1, 2, 3, 4][2, 3, 4, -1]
def next_greater_element(arr):
    """
    Find next greater element for each element
    Time: O(n)
    Space: O(n)
    """
    n = len(arr)
    result = [-1] * n
    stack = []  # Store indices
    
    for i in range(n):
        # Current value is greater than stack top's value
        while stack and arr[stack[-1]] < arr[i]:
            idx = stack.pop()
            result[idx] = arr[i]
        
        stack.append(i)
    
    return result

# Test
arr = [4, 5, 2, 10, 8]
print(next_greater_element(arr))
# [5, 10, 10, -1, -1]
from collections import deque

def bfs(graph, start):
    """
    Traverse graph using BFS
    """
    visited = set()
    queue = deque([start])
    visited.add(start)
    result = []
    
    while queue:
        node = queue.popleft()
        result.append(node)
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    
    return result

# Test
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

print(bfs(graph, 'A'))
# ['A', 'B', 'C', 'D', 'E', 'F']

4. Usage Patterns

Stack Usage Patterns

PatternDescriptionExamples
Bracket/Tag MatchingMatch opening and closingHTML, JSON, expressions
CalculatorEvaluate postfix notation3 4 + 2 * → 14
Function Call StackSimulate recursionDFS, backtracking
UndoCancel recent actionsCtrl+Z
Path TrackingRemember previous statesMaze exploration

Queue Usage Patterns

PatternDescriptionExamples
BFSLevel-order traversalShortest path, tree levels
Task QueueProcess in orderPrinter, call center
SchedulingRound-robinCPU scheduler
Sliding WindowFixed-size windowRecent N items
Stream ProcessingSequential dataLog analysis

5. Advanced Topics

Priority Queue

Priority queue is a queue where elements with higher priority come out first.

import heapq

# Min heap (default)
pq = []
heapq.heappush(pq, 3)
heapq.heappush(pq, 1)
heapq.heappush(pq, 2)

print(heapq.heappop(pq))  # 1 (smallest)
print(heapq.heappop(pq))  # 2
print(heapq.heappop(pq))  # 3

# Max heap (use negative values)
pq = []
heapq.heappush(pq, -3)
heapq.heappush(pq, -1)
heapq.heappush(pq, -2)

print(-heapq.heappop(pq))  # 3 (largest)

C++ Priority Queue:

#include <queue>
#include <iostream>
using namespace std;

int main() {
    // Max heap (default)
    priority_queue<int> pq;
    pq.push(3);
    pq.push(1);
    pq.push(2);
    
    cout << pq.top() << endl;  // 3 (largest)
    pq.pop();
    cout << pq.top() << endl;  // 2
    
    // Min heap
    priority_queue<int, vector<int>, greater<int>> min_pq;
    min_pq.push(3);
    min_pq.push(1);
    min_pq.push(2);
    
    cout << min_pq.top() << endl;  // 1 (smallest)
    
    return 0;
}

Deque - Double-Ended Queue

Deque allows insertion/deletion from both ends.

from collections import deque

dq = deque([1, 2, 3])

# Add/remove from rear
dq.append(4)        # [1, 2, 3, 4]
dq.pop()            # [1, 2, 3]

# Add/remove from front
dq.appendleft(0)    # [0, 1, 2, 3]
dq.popleft()        # [1, 2, 3]

# Check both ends
print(dq[0])   # 1 (front)
print(dq[-1])  # 3 (back)

# Rotate
dq.rotate(1)   # Right by 1 → [3, 1, 2]
dq.rotate(-1)  # Left by 1 → [1, 2, 3]

Troubleshooting

Issue 1: Pop from Empty Stack/Queue

# ❌ Error
stack = []
# stack.pop()  # IndexError: pop from empty list

# ✅ Solution: Check empty
if stack:
    value = stack.pop()
else:
    print("Stack is empty")

# ✅ Or exception handling
try:
    value = stack.pop()
except IndexError:
    print("Stack is empty")

Issue 2: Using list for Queue (Performance)

# ❌ Inefficient
queue = []
queue.append(1)
queue.pop(0)  # O(n)!

# ✅ Efficient
from collections import deque
queue = deque()
queue.append(1)
queue.popleft()  # O(1)

Issue 3: C++ stack/queue pop() Returns Nothing

// ❌ Error: pop() is void
// int value = stack.pop();  // Compile error

// ✅ Correct way
int value = stack.top();  // Get value
stack.pop();              // Remove

Summary

Key Points

  1. Stack: LIFO, push/pop O(1), parenthesis matching
  2. Queue: FIFO, enqueue/dequeue O(1), BFS
  3. Python: Use list for stack, deque for queue
  4. C++: <stack>, <queue> headers

Selection Guide

SituationData StructureReason
Process recent items firstStackLIFO
Process in orderQueueFIFO
Parenthesis matchingStackMatch most recent opening
BFSQueueLevel-order traversal
DFSStackDepth-first search
Undo/RedoStackCancel recent actions

Next Steps

  • Hash Table
  • BFS/DFS

Beginner

  • LeetCode 20: Valid Parentheses
  • LeetCode 225: Implement Stack using Queues
  • LeetCode 232: Implement Queue using Stacks

Intermediate

  • LeetCode 155: Min Stack
  • LeetCode 496: Next Greater Element I
  • LeetCode 739: Daily Temperatures

Advanced

  • LeetCode 84: Largest Rectangle in Histogram
  • LeetCode 239: Sliding Window Maximum
  • LeetCode 862: Shortest Subarray with Sum at Least K

  • Arrays and Lists | Essential Data Structures
  • Hash Table | O(1) Search Data Structure
  • BFS and DFS | Graph Traversal Algorithms

Keywords

Stack, Queue, Data Structure, LIFO, FIFO, Coding Interview, Algorithm, Python, C++, BFS, DFS, Deque, Priority Queue