DSA DAY ONE
DSA DAY ONE
PART I — FOUNDATIONS
Chapter 1: C++ Essentials for DSA
Chapter 2: C++ STL Deep Dive
Chapter 3: Complexity Analysis
PART II — LINEAR DATA STRUCTURES
Chapter 4: Arrays & Strings
Chapter 5: Two Pointers & Sliding Window
Chapter 6: Hashing
Chapter 7: Linked Lists
Chapter 8: Stacks
Chapter 9: Queues & Deques
Queue Fundamentals & STL `queue`Deque — Double-Ended QueueMonotonic Deque — Sliding Window MaximumCircular Queue — ImplementationBFS Foundation — Templates & VariantsSample ProblemsLeetCode Problem Set
PART III — TREES
Chapter 10: Binary Trees
Chapter 11: Binary Search Trees (BST)
Chapter 12: Heaps & Priority Queues
Chapter 13: Tries (Prefix Trees)
Chapter 14: Segment Tree & Fenwick Tree
PART IV — ALGORITHMS
Chapter 15: Sorting Algorithms
Chapter 16: Binary Search
Chapter 17: Recursion & Backtracking
Chapter 18: Graphs — Fundamentals & Traversals
Chapter 19: Graph Algorithms — Shortest Paths, MST & Union-Find
Chapter 20: Dynamic Programming
Chapter 21: Greedy Algorithms
Chapter 22: Divide & Conquer
PART V — COMPETITIVE & MATH
Chapter 23: Bit Manipulation
Chapter 24: Math & Number Theory
PART VI — PATTERNS
Chapter 25: Pattern Recognition Mastery
PART VII — SYSTEM DESIGN
Chapter 26: System Design for FAANG Interviews
PART VIII — INTERVIEW STRATEGY
Chapter 27: Cracking FAANG — Strategy & Communication
09

Queues & Deques


Chapter 9 ·  Part 2: LINEAR DATA STRUCTURES ·  Est. 25 min read

Chapter Goal: Master the queue family — from the fundamentals of FIFO processing to BFS, multi-source BFS, the monotonic deque (sliding window maximum), and 0-1 BFS. The queue is the engine behind shortest-path and level-order algorithms. The monotonic deque is one of the most underrated O(n) patterns in competitive programming.


9.1 Queue Fundamentals & STL queue

The FIFO Principle

A queue is a First-In, First-Out (FIFO) container. The element added first is the first to be removed. Think of a real-world queue (line) — you join at the back, leave from the front.

Enqueue 1, 2, 3:
front → [1][2][3] ← back

Dequeue → 1, then 2, then 3

STL queue — Interface

std::queue is an adapter wrapping a deque (default) with FIFO-restricted access.

#include <queue>
 
queue<int> q;
 
// --- Enqueue (add to back) ---
q.push(10);
q.push(20);
q.push(30);
q.emplace(40);    // Construct in-place
 
// --- Dequeue (remove from front) ---
q.pop();          // Removes front element (returns void)
 
// --- Access ---
cout << q.front();  // 20 — element at front (next to be removed)
cout << q.back();   // 40 — element at back (most recently added)
cout << q.size();   // 3
cout << q.empty();  // false
 
// Warning: Always check empty() before front(), back(), or pop()!
while (!q.empty()) {
    cout << q.front() << " ";
    q.pop();
}
// Output: 20 30 40

Complexity

Operation Time Notes
push / emplace O(1) amortized Enqueue at back
pop O(1) Dequeue from front
front O(1) Peek without removing
back O(1) Peek back without removing
size, empty O(1)

Queue vs. Stack — Side by Side

Property Queue (FIFO) Stack (LIFO)
Insert push (back) push (top)
Remove pop (front) pop (top)
Peek front() top()
Graph traversal BFS (level by level) DFS (deep first)
Shortest path Natural (unweighted) Not guaranteed
Bracket matching No Natural
Undo/redo No Natural

9.2 Deque — Double-Ended Queue

What Is a Deque?

std::deque (double-ended queue, pronounced "deck") supports O(1) amortized push and pop at both ends. Unlike vector, it does not store elements in a single contiguous block — it uses a sequence of fixed-size memory chunks, giving efficient insertions at both ends without copying.

#include <deque>
 
deque<int> dq;
 
// --- Front operations ---
dq.push_front(1);    // [1]
dq.push_front(0);    // [0, 1]
 
// --- Back operations ---
dq.push_back(2);     // [0, 1, 2]
dq.push_back(3);     // [0, 1, 2, 3]
 
// --- Remove ---
dq.pop_front();      // [1, 2, 3]
dq.pop_back();       // [1, 2]
 
// --- Access ---
cout << dq.front();  // 1
cout << dq.back();   // 2
cout << dq[0];       // 1 — O(1) random access (but slightly slower than vector)
cout << dq.size();   // 2

Complexity

Operation Time Notes
push_front, push_back O(1) amortized Key advantage over vector
pop_front, pop_back O(1) amortized Key advantage over vector
operator[], at() O(1) Slightly slower than vector due to indirect addressing
insert / erase (middle) O(n) Like vector
size, empty O(1)

When to Use Deque vs. Vector vs. Queue

Need O(1) at BOTH ends?               → deque
Need O(1) only at back?               → vector (faster)
Need FIFO (no random access)?         → queue (adapts deque)
Need monotonic window queries?        → deque directly (monotonic deque)
Underlying container for stack/queue? → deque (default)

Deque as a Sliding Window Container

The deque's ability to remove from both ends is exactly what makes it perfect for the sliding window maximum problem. When the window slides:

  • New element enters from the right (push_back or push_front)
  • Old element leaves from the left (pop_front when index out of window)
  • Stale elements leave from the right (pop_back when they can't be maximum)

9.3 Monotonic Deque — Sliding Window Maximum

The Problem

Given an array of n integers and a sliding window of size k, find the maximum element in each window position. Naive: O(nk). Optimal with monotonic deque: O(n).

nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
Windows:  [1,3,-1]  [3,-1,-3]  [-1,-3,5]  [-3,5,3]  [5,3,6]  [3,6,7]
Maxima:      3          3          5           5          6        7

The Monotonic Deque Invariant

Maintain a deque of indices in decreasing order of their values. The front is always the index of the current window's maximum.

Before pushing index i:

  1. Remove from back all indices whose values ≤ nums[i] — they can never be the window maximum while i is in the window (i is both newer and ≥ them).
  2. Remove from front if the front index is outside the current window (index ≤ i - k).
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    deque<int> dq;   // Stores indices; values in dq are decreasing (front = max)
    vector<int> result;
    int n = nums.size();
 
    for (int i = 0; i < n; i++) {
        // 1. Remove indices that are out of the current window [i-k+1, i]
        while (!dq.empty() && dq.front() < i - k + 1) dq.pop_front();
 
        // 2. Remove indices from back whose values are <= nums[i]
        //    (they are useless — nums[i] is newer and at least as large)
        while (!dq.empty() && nums[dq.back()] <= nums[i]) dq.pop_back();
 
        dq.push_back(i);
 
        // 3. Record result once the first full window is formed
        if (i >= k - 1) result.push_back(nums[dq.front()]);
    }
    return result;
}
// Time: O(n) — each index is pushed once and popped at most once
// Space: O(k) — deque holds at most k indices
 
/*
  nums=[1,3,-1,-3,5,3,6,7], k=3
  i=0: dq=[0]          (val=1)
  i=1: pop 0 (1<=3), dq=[1]    (val=3)
  i=2: dq=[1,2]        (val=3,3is>=−1). Window complete: max=nums[1]=3 ✓
  i=3: dq=[1,2,3]      (-3 smallest). Front=1 still in [1,3]. max=nums[1]=3 ✓
  i=4: pop 3(-3<=5), pop 2(-1<=5), pop 1(3<=5). dq=[4]. Front=4 in [2,4]. max=5 ✓
  i=5: dq=[4,5]        (3<5). Front=4 in [3,5]. max=nums[4]=5 ✓
  i=6: pop 5(3<=6), dq=[4,6]. Front=4 in [4,6]. max=nums[4]... wait 4 < 6-3+1=4
       4 is at boundary, 4 >= 4 so still in window. max=nums[4]=5? No...
       Actually i=6, window=[4,5,6], front=4 is in window. max=nums[4]=5? But 6 is there!
       Let me re-trace: after popping 5 (nums[5]=3 <= nums[6]=6), dq=[4,6].
       Then pop 4 (nums[4]=5 <= 6), dq=[6]. max=nums[6]=6 ✓
  i=7: dq=[6,7] (nums[7]=7>nums[6]=6, but 6<=7 so pop 6). dq=[7]. max=7 ✓
  Result: [3,3,5,5,6,7] ✓
*/

Generalization: Sliding Window Minimum

// Change only the comparison direction in step 2
while (!dq.empty() && nums[dq.back()] >= nums[i]) dq.pop_back();
// Now dq front = minimum of window

Application: Jump Game VI (LC 1696) — Monotonic Deque + DP

// dp[i] = max score to reach index i
// dp[i] = nums[i] + max(dp[i-k], dp[i-k+1], ..., dp[i-1])
int maxResult(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> dp(n);
    dp[0] = nums[0];
 
    deque<int> dq;   // Indices; dp values decreasing (front = max dp in window)
    dq.push_back(0);
 
    for (int i = 1; i < n; i++) {
        // Remove out-of-window indices from front
        while (!dq.empty() && dq.front() < i - k) dq.pop_front();
 
        // dp[i] = nums[i] + max dp in window
        dp[i] = nums[i] + dp[dq.front()];
 
        // Remove indices with dp value <= dp[i] from back
        while (!dq.empty() && dp[dq.back()] <= dp[i]) dq.pop_back();
        dq.push_back(i);
    }
    return dp[n - 1];
}
// Time: O(n), Space: O(n)

9.4 Circular Queue — Implementation

Why Circular Queues?

A naive array-based queue wastes space: after dequeuing, the front of the array becomes permanently unused. A circular queue treats the array as a ring — the rear wraps around to position 0 when it reaches the end.

Linear queue (wasteful):
  [_][_][_][1][2][3]  front=3, space before front is wasted
   0  1  2  3  4  5

Circular queue:
  After 3 enqueues + 3 dequeues + 3 more enqueues:
  [4][5][6][_][_][_]  front=3 (wraps), rear=0 (wraps back)
   0  1  2  3  4  5

Implementation (LC 622)

class MyCircularQueue {
    vector<int> data;
    int head, tail, size, capacity;
 
public:
    MyCircularQueue(int k) : data(k), head(0), tail(0), size(0), capacity(k) {}
 
    bool enQueue(int val) {
        if (isFull()) return false;
        data[tail] = val;
        tail = (tail + 1) % capacity;   // Wrap around
        size++;
        return true;
    }
 
    bool deQueue() {
        if (isEmpty()) return false;
        head = (head + 1) % capacity;  // Wrap around
        size--;
        return true;
    }
 
    int Front() { return isEmpty() ? -1 : data[head]; }
    int Rear()  { return isEmpty() ? -1 : data[(tail - 1 + capacity) % capacity]; }
    bool isEmpty() { return size == 0; }
    bool isFull()  { return size == capacity; }
};
// All operations: O(1). Space: O(k).
 
/*
  Key formula: next index = (current + 1) % capacity
  Rear value:  data[(tail - 1 + capacity) % capacity]  (the +capacity handles tail=0)
*/

9.5 BFS Foundation — Templates & Variants

BFS explores a graph layer by layer, guaranteeing the shortest path in unweighted graphs. The queue provides natural level-order processing.

Template 1 — Standard BFS (Shortest Path / Reachability)

// BFS from start node — returns distance to every reachable node
vector<int> bfs(int start, int n, vector<vector<int>>& adj) {
    vector<int> dist(n, -1);
    queue<int> q;
 
    dist[start] = 0;
    q.push(start);
 
    while (!q.empty()) {
        int node = q.front(); q.pop();
 
        for (int neighbor : adj[node]) {
            if (dist[neighbor] == -1) {    // Not visited yet
                dist[neighbor] = dist[node] + 1;
                q.push(neighbor);          // Warning: Mark as visited HERE (when enqueuing)
            }
        }
    }
    return dist;
}
// Time: O(V + E), Space: O(V)

Warning: Critical Rule: Mark nodes as visited when enqueuing, not when dequeuing. If you mark at dequeue, the same node can be enqueued multiple times before being processed, degrading from O(V+E) to exponential time.


Template 2 — Level-Order BFS (Process Layer by Layer)

Use this when the problem asks for results by level (tree level order, minimum steps, etc.).

// Process nodes level by level; track the current depth
void bfsLevelOrder(TreeNode* root) {
    if (!root) return;
    queue<TreeNode*> q;
    q.push(root);
 
    int level = 0;
    while (!q.empty()) {
        int levelSize = q.size();   // Snapshot of current level's size
 
        for (int i = 0; i < levelSize; i++) {
            TreeNode* node = q.front(); q.pop();
            cout << node->val << " ";   // Process node at this level
 
            if (node->left)  q.push(node->left);
            if (node->right) q.push(node->right);
        }
        cout << "\n";
        level++;
    }
}
// Time: O(n), Space: O(n) — at most O(width) nodes in queue at once

Variant: Capture results by level

vector<vector<int>> levelOrder(TreeNode* root) {
    if (!root) return {};
    vector<vector<int>> result;
    queue<TreeNode*> q;
    q.push(root);
 
    while (!q.empty()) {
        int sz = q.size();
        result.emplace_back();
        for (int i = 0; i < sz; i++) {
            TreeNode* node = q.front(); q.pop();
            result.back().push_back(node->val);
            if (node->left)  q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    return result;
}

Template 3 — BFS on a Grid

// BFS on 2D grid to find shortest path from src to dst
// Returns minimum steps, or -1 if unreachable
int bfsGrid(vector<vector<int>>& grid, pair<int,int> src, pair<int,int> dst) {
    int rows = grid.size(), cols = grid[0].size();
    vector<vector<bool>> visited(rows, vector<bool>(cols, false));
    queue<pair<int,int>> q;
 
    int dx[] = {0, 0, 1, -1};
    int dy[] = {1, -1, 0, 0};
 
    visited[src.first][src.second] = true;
    q.push(src);
    int steps = 0;
 
    while (!q.empty()) {
        int sz = q.size();
        for (int i = 0; i < sz; i++) {
            auto [x, y] = q.front(); q.pop();
            if (x == dst.first && y == dst.second) return steps;
 
            for (int d = 0; d < 4; d++) {
                int nx = x + dx[d], ny = y + dy[d];
                if (nx >= 0 && nx < rows && ny >= 0 && ny < cols
                    && !visited[nx][ny] && grid[nx][ny] != BLOCKED) {
                    visited[nx][ny] = true;
                    q.push({nx, ny});
                }
            }
        }
        steps++;
    }
    return -1;   // Unreachable
}
// Time: O(rows × cols), Space: O(rows × cols)

Template 4 — Multi-Source BFS

Start BFS from multiple source nodes simultaneously. This finds the minimum distance from any source to every reachable node in one pass.

// Multi-source BFS — initialize queue with ALL sources at distance 0
void multiSourceBFS(vector<vector<int>>& grid,
                    vector<pair<int,int>>& sources,
                    vector<vector<int>>& dist) {
    int rows = grid.size(), cols = grid[0].size();
    queue<pair<int,int>> q;
    int dx[] = {0, 0, 1, -1};
    int dy[] = {1, -1, 0, 0};
 
    // Initialize ALL sources at distance 0
    for (auto [r, c] : sources) {
        dist[r][c] = 0;
        q.push({r, c});
    }
 
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
 
        for (int d = 0; d < 4; d++) {
            int nx = x + dx[d], ny = y + dy[d];
            if (nx >= 0 && nx < rows && ny >= 0 && ny < cols
                && dist[nx][ny] == -1) {          // Not yet reached
                dist[nx][ny] = dist[x][y] + 1;
                q.push({nx, ny});
            }
        }
    }
}
// Applications: Rotting Oranges, 01 Matrix, Walls and Gates

Template 5 — 0-1 BFS (Deque for Binary-Weight Graphs)

When edge weights are only 0 or 1, use a deque instead of a priority queue. Push to front for 0-weight edges, push to back for 1-weight edges. Achieves O(V + E) instead of O((V + E) log V) for Dijkstra.

vector<int> zeroOneBFS(int start, int n, vector<vector<pair<int,int>>>& adj) {
    // adj[u] = {(v, weight)} where weight is 0 or 1
    vector<int> dist(n, INT_MAX);
    deque<int> dq;
 
    dist[start] = 0;
    dq.push_front(start);
 
    while (!dq.empty()) {
        int u = dq.front(); dq.pop_front();
 
        for (auto [v, w] : adj[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (w == 0) dq.push_front(v);   // 0-weight: front (processed first)
                else        dq.push_back(v);     // 1-weight: back (processed later)
            }
        }
    }
    return dist;
}
// Time: O(V + E), Space: O(V)

BFS vs. DFS — When to Use Which

Criterion BFS DFS
Shortest path (unweighted) Guaranteed Not guaranteed
Level-order / distance by layers Natural Needs tracking
Memory for wide graphs Warning: O(width) — can be large O(depth) — usually smaller
Memory for deep graphs O(depth) better Warning: Stack overflow risk
Cycle detection Both work Both work
Topological sort Kahn's (BFS) DFS-based
Connected components Both work Both work
Find all paths Less natural Natural
Tree problems (height, etc.) Both work Usually simpler

9.6 Sample Problems


Problem 1 — Binary Tree Level Order Traversal (Medium) — LC 102

vector<vector<int>> levelOrder(TreeNode* root) {
    if (!root) return {};
    vector<vector<int>> result;
    queue<TreeNode*> q;
    q.push(root);
 
    while (!q.empty()) {
        int sz = q.size();
        result.emplace_back();
        for (int i = 0; i < sz; i++) {
            TreeNode* node = q.front(); q.pop();
            result.back().push_back(node->val);
            if (node->left)  q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    return result;
}
// Time: O(n), Space: O(n)

Problem 2 — Number of Islands (Medium) — LC 200

Count the number of islands (groups of connected '1's) in a 2D grid.

int numIslands(vector<vector<char>>& grid) {
    int rows = grid.size(), cols = grid[0].size(), count = 0;
    int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
 
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (grid[i][j] == '1') {
                count++;
                // BFS to mark the entire island as visited
                queue<pair<int,int>> q;
                grid[i][j] = '0';   // Mark visited in-place
                q.push({i, j});
 
                while (!q.empty()) {
                    auto [x, y] = q.front(); q.pop();
                    for (int d = 0; d < 4; d++) {
                        int nx = x+dx[d], ny = y+dy[d];
                        if (nx>=0 && nx<rows && ny>=0 && ny<cols && grid[nx][ny]=='1') {
                            grid[nx][ny] = '0';   // Mark as visited when enqueuing
                            q.push({nx, ny});
                        }
                    }
                }
            }
        }
    }
    return count;
}
// Time: O(rows × cols), Space: O(min(rows, cols)) — queue width at most diagonal

Problem 3 — Rotting Oranges (Medium) — LC 994

Each minute, fresh oranges adjacent to rotten ones become rotten. Find the minimum time for all oranges to rot, or -1 if impossible.

int orangesRotting(vector<vector<int>>& grid) {
    int rows = grid.size(), cols = grid[0].size();
    queue<pair<int,int>> q;
    int fresh = 0;
    int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
 
    // Multi-source BFS: initialize ALL rotten oranges at time=0
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++) {
            if (grid[i][j] == 2) q.push({i, j});
            if (grid[i][j] == 1) fresh++;
        }
 
    if (fresh == 0) return 0;
 
    int minutes = 0;
    while (!q.empty()) {
        int sz = q.size();
        for (int i = 0; i < sz; i++) {
            auto [x, y] = q.front(); q.pop();
            for (int d = 0; d < 4; d++) {
                int nx = x+dx[d], ny = y+dy[d];
                if (nx>=0 && nx<rows && ny>=0 && ny<cols && grid[nx][ny]==1) {
                    grid[nx][ny] = 2;    // Infect
                    fresh--;
                    q.push({nx, ny});
                }
            }
        }
        if (!q.empty()) minutes++;   // Only count if something happened
    }
    return fresh == 0 ? minutes : -1;
}
// Time: O(rows × cols), Space: O(rows × cols)

Problem 4 — 01 Matrix (Medium) — LC 542

Find the distance from each cell to the nearest 0.

Key insight: Multi-source BFS starting from ALL zeros simultaneously. Every 1 will receive the distance from its closest 0.

vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
    int rows = mat.size(), cols = mat[0].size();
    vector<vector<int>> dist(rows, vector<int>(cols, -1));
    queue<pair<int,int>> q;
    int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
 
    // Initialize all zeros as sources (distance 0)
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            if (mat[i][j] == 0) { dist[i][j] = 0; q.push({i, j}); }
 
    // Multi-source BFS — expands outward from all zeros simultaneously
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        for (int d = 0; d < 4; d++) {
            int nx = x+dx[d], ny = y+dy[d];
            if (nx>=0 && nx<rows && ny>=0 && ny<cols && dist[nx][ny]==-1) {
                dist[nx][ny] = dist[x][y] + 1;
                q.push({nx, ny});
            }
        }
    }
    return dist;
}
// Time: O(rows × cols), Space: O(rows × cols)

Problem 5 — Word Ladder (Hard) — LC 127

Find the shortest transformation sequence from beginWord to endWord, changing one letter at a time, using only words in the word list.

int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
    unordered_set<string> wordSet(wordList.begin(), wordList.end());
    if (!wordSet.count(endWord)) return 0;
 
    queue<string> q;
    q.push(beginWord);
    wordSet.erase(beginWord);
    int steps = 1;
 
    while (!q.empty()) {
        int sz = q.size();
        for (int i = 0; i < sz; i++) {
            string word = q.front(); q.pop();
            if (word == endWord) return steps;
 
            // Try all one-letter mutations
            for (int j = 0; j < (int)word.size(); j++) {
                char original = word[j];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    word[j] = c;
                    if (wordSet.count(word)) {
                        wordSet.erase(word);   // Mark visited by removing from set
                        q.push(word);
                    }
                }
                word[j] = original;   // Restore
            }
        }
        steps++;
    }
    return 0;
}
// Time: O(M² × N) where M = word length, N = word list size
// Space: O(M × N)

Problem 6 — Sliding Window Maximum (Hard) — LC 239

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    deque<int> dq;   // Indices; values monotonically decreasing
    vector<int> result;
 
    for (int i = 0; i < (int)nums.size(); i++) {
        // Remove out-of-window indices from front
        while (!dq.empty() && dq.front() < i - k + 1) dq.pop_front();
        // Remove smaller values from back — they can never be window maximum
        while (!dq.empty() && nums[dq.back()] <= nums[i]) dq.pop_back();
 
        dq.push_back(i);
        if (i >= k - 1) result.push_back(nums[dq.front()]);
    }
    return result;
}
// Time: O(n), Space: O(k)

Problem 7 — Design Circular Queue (Medium) — LC 622

class MyCircularQueue {
    vector<int> data;
    int head, tail, size, cap;
public:
    MyCircularQueue(int k) : data(k), head(0), tail(0), size(0), cap(k) {}
    bool enQueue(int val) {
        if (isFull()) return false;
        data[tail] = val; tail = (tail+1) % cap; size++; return true;
    }
    bool deQueue() {
        if (isEmpty()) return false;
        head = (head+1) % cap; size--; return true;
    }
    int  Front()   { return isEmpty() ? -1 : data[head]; }
    int  Rear()    { return isEmpty() ? -1 : data[(tail-1+cap) % cap]; }
    bool isEmpty() { return size == 0; }
    bool isFull()  { return size == cap; }
};
// All operations: O(1)

Problem 8 — Shortest Path in Binary Matrix (Medium) — LC 1091

Find shortest path from top-left to bottom-right through 0s (8-directional).

int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
    int n = grid.size();
    if (grid[0][0] == 1 || grid[n-1][n-1] == 1) return -1;
    if (n == 1) return 1;
 
    queue<pair<int,int>> q;
    grid[0][0] = 1;   // Mark visited (reuse grid to save space)
    q.push({0, 0});
    int steps = 1;
 
    int dx[] = {-1,-1,-1, 0, 0, 1, 1, 1};
    int dy[] = {-1, 0, 1,-1, 1,-1, 0, 1};
 
    while (!q.empty()) {
        int sz = q.size();
        steps++;
        for (int i = 0; i < sz; i++) {
            auto [x, y] = q.front(); q.pop();
            for (int d = 0; d < 8; d++) {
                int nx = x+dx[d], ny = y+dy[d];
                if (nx>=0 && nx<n && ny>=0 && ny<n && grid[nx][ny]==0) {
                    if (nx==n-1 && ny==n-1) return steps;
                    grid[nx][ny] = 1;
                    q.push({nx, ny});
                }
            }
        }
    }
    return -1;
}
// Time: O(n²), Space: O(n²)

Problem 9 — Jump Game VI (Medium) — LC 1696

From index i, jump to any index in [i+1, i+k]. Maximize total score.

int maxResult(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> dp(n);
    dp[0] = nums[0];
    deque<int> dq;
    dq.push_back(0);
 
    for (int i = 1; i < n; i++) {
        while (!dq.empty() && dq.front() < i - k) dq.pop_front();
        dp[i] = nums[i] + dp[dq.front()];
        while (!dq.empty() && dp[dq.back()] <= dp[i]) dq.pop_back();
        dq.push_back(i);
    }
    return dp[n - 1];
}
// Time: O(n), Space: O(n)

Problem 10 — Open the Lock (Medium) — LC 752

Starting from "0000", find minimum turns to reach target avoiding deadends.

int openLock(vector<string>& deadends, string target) {
    unordered_set<string> dead(deadends.begin(), deadends.end());
    if (dead.count("0000")) return -1;
    if (target == "0000")   return 0;
 
    unordered_set<string> visited;
    queue<string> q;
    q.push("0000");
    visited.insert("0000");
    int turns = 0;
 
    while (!q.empty()) {
        int sz = q.size();
        turns++;
        for (int i = 0; i < sz; i++) {
            string curr = q.front(); q.pop();
            // Try all 8 moves (4 wheels × 2 directions)
            for (int w = 0; w < 4; w++) {
                for (int dir : {1, -1}) {
                    string next = curr;
                    next[w] = (next[w] - '0' + dir + 10) % 10 + '0';
                    if (next == target) return turns;
                    if (!dead.count(next) && !visited.count(next)) {
                        visited.insert(next);
                        q.push(next);
                    }
                }
            }
        }
    }
    return -1;
}
// Time: O(10^4 × 4 × 2) = O(80000), Space: O(10^4)

Problem 11 — Maximum Width of Binary Tree (Medium) — LC 662

Find the maximum width of a binary tree (distance between leftmost and rightmost non-null nodes at any level).

int widthOfBinaryTree(TreeNode* root) {
    if (!root) return 0;
    int maxWidth = 0;
    // Queue of {node, position_index}
    // Left child of node at pos p → 2p, right child → 2p+1
    queue<pair<TreeNode*, long long>> q;
    q.push({root, 0});
 
    while (!q.empty()) {
        int sz = q.size();
        long long leftmost = q.front().second;
        long long rightmost = leftmost;
 
        for (int i = 0; i < sz; i++) {
            auto [node, pos] = q.front(); q.pop();
            // Normalize position to prevent overflow: subtract leftmost
            long long normPos = pos - leftmost;
            rightmost = normPos;
 
            if (node->left)  q.push({node->left,  2 * normPos});
            if (node->right) q.push({node->right, 2 * normPos + 1});
        }
        maxWidth = max(maxWidth, (int)(rightmost + 1));
    }
    return maxWidth;
}
// Time: O(n), Space: O(n)
// Key: normalize positions each level to prevent long long overflow

Problem 12 — Walls and Gates (Medium) — LC 286

In a grid with walls (-1), gates (0), and empty rooms (INF), fill each room with its distance to the nearest gate.

void wallsAndGates(vector<vector<int>>& rooms) {
    if (rooms.empty()) return;
    int rows = rooms.size(), cols = rooms[0].size();
    const int INF = 2147483647;
    queue<pair<int,int>> q;
    int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
 
    // Multi-source BFS: start from ALL gates simultaneously
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            if (rooms[i][j] == 0) q.push({i, j});
 
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        for (int d = 0; d < 4; d++) {
            int nx = x+dx[d], ny = y+dy[d];
            if (nx>=0 && nx<rows && ny>=0 && ny<cols && rooms[nx][ny]==INF) {
                rooms[nx][ny] = rooms[x][y] + 1;
                q.push({nx, ny});
            }
        }
    }
}
// Time: O(rows × cols), Space: O(rows × cols)

9.7 LeetCode Problem Set

# Problem Difficulty Core Technique Link
1 Binary Tree Level Order Traversal Medium Level-order BFS LC 102
2 Number of Islands Medium BFS flood-fill on grid LC 200
3 Rotting Oranges Medium Multi-source BFS LC 994
4 01 Matrix Medium Multi-source BFS from 0s LC 542
5 Design Circular Queue Medium Modular arithmetic array LC 622
6 Maximum Width of Binary Tree Medium BFS with positional indexing LC 662
7 Word Ladder Hard BFS on state space LC 127
8 Sliding Window Maximum Hard Monotonic deque LC 239

Suggested order: #1 (Level Order) is the essential BFS tree problem. #2 (Islands) and #3 (Rotting Oranges) teach single vs. multi-source BFS. #4 (01 Matrix) reinforces multi-source BFS with distance propagation. #7 (Word Ladder) is the canonical BFS-on-state-space hard problem — master this and you can solve any BFS variant. #8 (Sliding Window Max) is the monotonic deque benchmark — solve it until it feels trivial.


← PreviousChapter 8: StacksNext →Chapter 10: Binary Trees
Chapter 9 — Progress
0 / 26 COMPLETE
Queue & Deque Fundamentals
  • Know all six STL queue operations (push, pop, front, back, size, empty)
  • Know `front()` returns the oldest element (first to be removed)
  • Know `back()` returns the newest element (last added)
  • Know all four deque operations: push_front, push_back, pop_front, pop_back
  • Explain when deque beats vector: when O(1) push/pop at front is needed
Monotonic Deque
  • State the two maintenance rules: remove out-of-window from front; remove non-maximums from back
  • Implement Sliding Window Maximum from memory (O(n) time, O(k) space)
  • Implement Sliding Window Minimum (change only the comparison direction)
  • Apply monotonic deque to DP (Jump Game VI pattern)
  • State the amortized argument: each index pushed and popped at most once → O(n)
Circular Queue
  • Implement circular queue with the formula `(index + 1) % capacity`
  • Correctly compute the rear index: `(tail - 1 + capacity) % capacity`
  • Know that circular queues avoid wasted space from linear shifting
BFS Templates
  • Write the standard BFS template (mark visited WHEN ENQUEUING)
  • Write the level-order BFS template (snapshot `q.size()` at start of each level)
  • Write the 2D grid BFS template (4-directional with bounds check)
  • Write the multi-source BFS template (initialize queue with ALL sources)
  • Implement 0-1 BFS: push_front for 0-weight, push_back for 1-weight
  • Explain BFS guarantees shortest path in unweighted graphs (level-by-level expansion)
Visited Marking — Critical Rule
  • Always mark nodes as visited WHEN ENQUEUING, not when dequeuing
  • Understand why marking at dequeue causes the same node to be enqueued multiple times
BFS on Graphs / Grids
  • Solve Number of Islands using BFS flood-fill (in-place marking)
  • Solve Rotting Oranges using multi-source BFS
  • Solve 01 Matrix using multi-source BFS from all zeros
  • Solve Word Ladder using BFS on state space (try all mutations)
  • Solve Maximum Width of Binary Tree using BFS with positional normalization
Notes▸