Pattern 01 · Foundations

Hash, Stack & Queue

The three workhorse structures behind almost every other pattern. A hash map buys you O(1) lookup; a stack remembers the most recent thing; a queue remembers the oldest. Pick the right one and half the problem disappears.

ops O(1)space O(n)variants dict · stack · queuepractice 14 problems
01 hash map

Hash map: trade space for instant lookup

A hash map converts a key into an array index via a hash function. When two keys land on the same index (a collision), they chain together in the same bucket — which is why worst case degrades to O(n), even though in practice every operation is O(1).

Key Hash Function Buckets (array) ---- ------------- --------------- "cat" --> hash("cat")=2 --> [ 0 ] -> None "dog" --> hash("dog")=0 --> [ 1 ] -> ("fox", 7) "fox" --> hash("fox")=1 --> [ 2 ] -> ("cat", 3) "ant" --> hash("ant")=0 --> [ 3 ] -> None Bucket 0 collision chain: ("dog", 5) -> ("ant", 9) -> None

ToolkitThe Python flavors

StructureWhat it adds
dictBasic key-value store.
defaultdictAuto-initializes missing keys — no if key not in d dance.
CounterSpecialized for counting occurrences.
OrderedDictRemembers insertion order with move_to_end / popitem — the LRU building block.
setA hash map with only keys, no values. Membership checks.
OperationAverageWorst case
LookupO(1)O(n)
InsertO(1)O(n)
DeleteO(1)O(n)
IterateO(n)O(n)

Worst case requires pathological hash collisions — almost never happens in practice.

02 stack

Stack: last in, first out

Think of stacking plates: you always push onto the top, pop from the top, and can peek at the top without removing. The most recently added item is always the first one back out.

Push 'A' Push 'B' Push 'C' Pop Pop | | | | | 'C' | <-- top | | | | | | | 'B' | | 'B' | | 'B' | | | | 'A' | | 'A' | | 'A' | | 'A' | | 'A' | +-----+ +-----+ +-----+ +-----+ +-----+
stack_ops.pypython
stack = []
stack.append(item)    # push  - O(1)
stack.pop()           # pop   - O(1)
stack[-1]             # peek  - O(1)
len(stack) == 0       # empty check

Use cases: matching brackets, expression evaluation (postfix, infix), undo/redo, DFS with an explicit stack, monotonic-stack problems (next greater element), and simulating the call stack.

TraceValid parentheses, live

The canonical stack problem: every closing bracket must match the most recent unmatched opener. "Most recent" is the giveaway — that's LIFO.

1 / 8walkthrough — valid parentheses on "([{}])"
(
[
{
}
]
)
stack &empty;
Scan left to right. Openers get pushed; each closer must match the stack top — the most recent unmatched opener. That recency rule is exactly LIFO.
03 queue

Queue: first in, first out

A line at a store: enqueue at the back, dequeue from the front, and the first person in line is the first one served.

Enqueue: add to back Dequeue: remove from front Front Back | | v v +---+ +---+ +---+ +---+ | A |->| B |->| C |->| D | +---+ +---+ +---+ +---+ ^ ^ | | dequeue enqueue (exit) (enter)
queue_ops.pypython
from collections import deque

queue = deque()
queue.append(item)     # enqueue (right) - O(1)
queue.popleft()        # dequeue (left)  - O(1)
queue[0]               # peek front      - O(1)
len(queue) == 0        # empty check
Never use list.pop(0) for a queue
It's O(n) — every remaining element shifts left by one. deque.popleft() is O(1).

Use cases: BFS traversal, level-order tree traversal, sliding window maximum (with a deque), task scheduling / round-robin, time-based rate limiting.

1 / 8walkthrough — queue order of service (fifo)
A
B
C
D
queue &empty;
served —
Four tasks arrive over time. We enqueue at the back, dequeue from the front — highlighted cells are what's currently waiting in the queue.
04 recognition

Reading the problem signal

Problem signalData structure
"find pair that sums to…"Hash map
"count occurrences"Counter
"group by property"defaultdict(list)
"check for duplicates"Set
"matching brackets"Stack
"evaluate expression"Stack
"next greater element"Monotonic stack
"undo last action"Stack
"shortest path"Queue (BFS)
"level order traversal"Queue
"process in order"Queue
"sliding window max/min"Deque

HashWhich hash flavor?

Start | v Need fast lookup by key? ----YES----> Hash Map (dict) | | NO Need default values? | | v YES --> defaultdict Need to count things? ---YES----> Counter | NO | v Need to check membership? ---YES----> Set | NO | v Probably not a hash problem.

StackFour questions that reveal a stack

  • Is there matching or nesting? Parentheses ( [ { } ] ), HTML tags — yes → stack.
  • Do I need the most recent unprocessed item? Next greater element, daily temperatures — yes → stack (probably monotonic).
  • Am I evaluating an expression? Calculator problems, Reverse Polish Notation — yes → stack (maybe two stacks).
  • Do I need to undo or go back? Browser back button, backspace in string — yes → stack.
05 key insight

The complement pattern

The single most common hash map trick: instead of searching for a match, store what you need and check whether the current element is what a previous element needed.

For Two Sum: target=9, array=[2, 7, 11, 15] Step 1: see 2, need 7, store {2: 0} Step 2: see 7, need 2, CHECK -> 2 is in map! Found pair (0, 1) Instead of: "for each element, scan all others" O(n^2) We get: "for each element, check the map" O(n)
Why it works
Every pair has two chances to be found: when its first element is stored, and when its second element arrives and looks the first one up. One pass, one O(1) lookup per element — the nested loop collapses into a dictionary.
06 templates

The snippets to memorize

S1Two Sum — complement lookup

two_sum.py — LC 1python
def two_sum(nums, target):
    """Find two indices whose values sum to target."""
    seen = {}                           # value -> index
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:          # O(1) lookup
            return [seen[complement], i]
        seen[num] = i                   # store for future lookups
    return []

S2Valid Parentheses — stack matching

valid_parentheses.py — LC 20python
def is_valid(s):
    """Check if brackets are properly matched and nested."""
    stack = []
    match = {')': '(', ']': '[', '}': '{'}  # closing -> opening

    for char in s:
        if char in match:               # closing bracket
            if not stack or stack[-1] != match[char]:
                return False            # nothing to match, or mismatch
            stack.pop()                 # matched -- remove opening
        else:
            stack.append(char)          # opening bracket -- push

    return len(stack) == 0              # all brackets must be matched

S3Group Anagrams — defaultdict grouping

group_anagrams.py — LC 49python
from collections import defaultdict

def group_anagrams(strs):
    """Group strings that are anagrams of each other."""
    groups = defaultdict(list)
    for s in strs:
        # Sorted characters form a canonical key for anagrams
        # "eat" -> "aet", "tea" -> "aet", "ate" -> "aet"
        key = tuple(sorted(s))
        groups[key].append(s)
    return list(groups.values())

S4Longest Consecutive Sequence — set + sequence start

longest_consecutive.py — LC 128python
def longest_consecutive(nums):
    """Find length of longest consecutive element sequence. O(n)."""
    num_set = set(nums)
    best = 0

    for num in num_set:
        # Only start counting from the BEGINNING of a sequence.
        # This check ensures each sequence is counted exactly once.
        if num - 1 not in num_set:      # <-- the key insight
            length = 1
            while num + length in num_set:
                length += 1
            best = max(best, length)

    return best

S5Min Stack — two parallel stacks

min_stack.py — LC 155python
class MinStack:
    """Stack that supports O(1) push, pop, top, and getMin."""

    def __init__(self):
        self.stack = []
        self.min_stack = []             # tracks minimum at each level

    def push(self, val):
        self.stack.append(val)
        # Push the new minimum (compare with current min)
        min_val = min(val, self.min_stack[-1] if self.min_stack else val)
        self.min_stack.append(min_val)

    def pop(self):
        self.stack.pop()
        self.min_stack.pop()            # min_stack stays in sync

    def top(self):
        return self.stack[-1]

    def getMin(self):
        return self.min_stack[-1]

#  stack:     [5, 3, 7, 2]     min_stack: [5, 3, 3, 2]
#  After pop: [5, 3, 7]        min_stack: [5, 3, 3]
#  getMin() = 3  (correct!)

S6LRU Cache — OrderedDict

lru_cache.py — LC 146python
from collections import OrderedDict

class LRUCache:
    """Least Recently Used cache with O(1) get and put."""

    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.capacity = capacity

    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)      # mark as recently used
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)  # mark as recently used
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)  # evict LEAST recent (front)

S7Reverse Polish Notation — stack evaluation

eval_rpn.py — LC 150python
def eval_rpn(tokens):
    """Evaluate Reverse Polish Notation using a stack."""
    stack = []
    ops = {
        '+': lambda a, b: a + b,
        '-': lambda a, b: a - b,
        '*': lambda a, b: a * b,
        '/': lambda a, b: int(a / b),   # truncate toward zero
    }
    for token in tokens:
        if token in ops:
            b = stack.pop()             # second operand (top)
            a = stack.pop()             # first operand
            stack.append(ops[token](a, b))
        else:
            stack.append(int(token))
    return stack[0]
07 complexity

Complexity quick reference

Data structureAccessSearchInsertDeleteSpace
Hash map (dict)O(1)*O(1)*O(1)*O(1)*O(n)
Set—O(1)*O(1)*O(1)*O(n)
Stack (list)O(n)O(n)O(1)**O(1)**O(n)
Queue (deque)O(n)O(n)O(1)O(1)O(n)

* amortized average, worst case O(n)  ·  ** push/pop at the top only

08 common mistakes

Where the foundations crack

Mistake 1 — list.pop(0) as a queue
O(n) per dequeue because every element shifts. Use deque.popleft().
Mistake 2 — popping an empty stack
Always guard with if stack: before calling pop() or reading stack[-1].
Mistake 3 — mutating a dict while iterating
Raises RuntimeError. Iterate over a copy: for k in list(d):.
Mistake 4 — unhashable keys
Lists can't be dict keys or set members. Convert to a tuple first (see the Group Anagrams key).
Mistake 5 — storing before checking in complement lookup
For "two distinct indices," storing first can match an element with itself.
the fixpython
# WRONG for "find two distinct indices":
seen[num] = i
if complement in seen: ...   # might find itself

# RIGHT:
if complement in seen: ...   # check first
seen[num] = i                # store after
Mistake 6 — forgetting the leftover-stack check
In matching problems, "((" never triggers a mismatch — it just leaves items behind. Always finish with len(stack) == 0.
10 practice

Practice ladder

In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.

14 problems
easyLC 1
Hash map complement lookup. The warm-up.
easyLC 20
Stack matching — the trace above, in code.
easyLC 217
Set membership check.
easyLC 242
Counter comparison.
easyLC 496
Monotonic stack + hash map of answers.
mediumLC 49
defaultdict + sorted-tuple key.
mediumLC 128
Set + start-of-sequence check.
mediumLC 155
Parallel min stack, kept in sync.
mediumLC 150
Stack evaluation — pop two, push result.
mediumLC 227
Stack + operator precedence.
mediumLC 146
OrderedDict — move_to_end + popitem.
mediumLC 394
Stack of (string, count) pairs for nesting.
mediumLC 739
Monotonic decreasing stack (see Pattern 14).
hardLC 84
The monotonic-stack boss fight.