Problems
Put your queue knowledge to the test with these curated LeetCode problems. Start from the top and work your way through. Refer to our Code Templates for reusable implementations.
Easy Problems
1. Implement Queue using Stacks
- Brief: Implement a FIFO queue using only two stacks.
- Why this pattern?: Shows how to convert LIFO to FIFO structure using two stacks as temporary storage.
- Key Insight: When the output stack is empty, transfer all elements from input stack. This reverses the order, giving us FIFO behavior.
graph TD
subgraph Operation["Push 1, 2, 3"]
A[Input Stack: 1, 2, 3] --> B[Pop]
end
subgraph Transfer["Transfer to Output"]
B --> C[Output Stack: 3, 2, 1]
end
subgraph Result["Pop Operations"]
C --> D[Returns 1]
D --> E[Returns 2]
end
class MyQueue {
constructor() {
this.inStack = [];
this.outStack = [];
}
push(x) {
this.inStack.push(x);
}
pop() {
this._moveToOutStack();
return this.outStack.pop();
}
peek() {
this._moveToOutStack();
return this.outStack[this.outStack.length - 1];
}
empty() {
return this.inStack.length === 0 && this.outStack.length === 0;
}
_moveToOutStack() {
if (this.outStack.length === 0) {
while (this.inStack.length > 0) {
this.outStack.push(this.inStack.pop());
}
}
}
}class MyQueue:
def __init__(self):
self.in_stack = []
self.out_stack = []
def push(self, x: int) -> None:
self.in_stack.append(x)
def pop(self) -> int:
self._move_to_out_stack()
return self.out_stack.pop()
def peek(self) -> int:
self._move_to_out_stack()
return self.out_stack[-1]
def empty(self) -> bool:
return len(self.in_stack) == 0 and len(self.out_stack) == 0
def _move_to_out_stack(self):
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())import java.util.Stack;
class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
moveToOutStack();
return outStack.pop();
}
public int peek() {
moveToOutStack();
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void moveToOutStack() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
}#include <stack>
class MyQueue {
private:
std::stack<int> inStack;
std::stack<int> outStack;
void moveToOutStack() {
if (outStack.empty()) {
while (!inStack.empty()) {
outStack.push(inStack.top());
inStack.pop();
}
}
}
public:
MyQueue() {}
void push(int x) {
inStack.push(x);
}
int pop() {
moveToOutStack();
int val = outStack.top();
outStack.pop();
return val;
}
int peek() {
moveToOutStack();
return outStack.top();
}
bool empty() {
return inStack.empty() && outStack.empty();
}
};type MyQueue struct {
inStack []int
outStack []int
}
func Constructor() MyQueue {
return MyQueue{}
}
func (q *MyQueue) Push(x int) {
q.inStack = append(q.inStack, x)
}
func (q *MyQueue) Pop() int {
q.moveToOutStack()
val := q.outStack[len(q.outStack)-1]
q.outStack = q.outStack[:len(q.outStack)-1]
return val
}
func (q *MyQueue) Peek() int {
q.moveToOutStack()
return q.outStack[len(q.outStack)-1]
}
func (q *MyQueue) Empty() bool {
return len(q.inStack) == 0 && len(q.outStack) == 0
}
func (q *MyQueue) moveToOutStack() {
if len(q.outStack) == 0 {
for len(q.inStack) > 0 {
n := len(q.inStack) - 1
q.outStack = append(q.outStack, q.inStack[n])
q.inStack = q.inStack[:n]
}
}
}class MyQueue
def initialize
@in_stack = []
@out_stack = []
end
def push(x)
@in_stack.push(x)
end
def pop
move_to_out_stack
@out_stack.pop
end
def peek
move_to_out_stack
@out_stack.last
end
def empty
@in_stack.empty? && @out_stack.empty?
end
private
def move_to_out_stack
if @out_stack.empty?
until @in_stack.empty?
@out_stack.push(@in_stack.pop)
end
end
end
endTime Complexity: Amortized
O(1)
O(1)
O(n)
2. Number of Recent Calls
- Brief: Count the number of calls in the last 3000 milliseconds.
- Why this pattern?: Queue naturally handles time-based sliding window by removing expired timestamps.
- Key Insight: Maintain timestamps in queue, removing those outside the 3000ms window before counting.
sequenceDiagram
participant Queue as Queue
participant Time as Current Time
Queue->>Time: t=100, push<br/>Queue: [100]
Queue->>Time: t=3001, remove<br/>Queue: []
Queue->>Time: t=3002, push<br/>Queue: [3002]
Note over Queue: Window: t-3000 to t
class RecentCounter {
constructor() {
this.requests = [];
}
ping(t) {
this.requests.push(t);
while (this.requests.length > 0 && this.requests[0] < t - 3000) {
this.requests.shift();
}
return this.requests.length;
}
}from collections import deque
class RecentCounter:
def __init__(self):
self.requests = deque()
def ping(self, t: int) -> int:
self.requests.append(t)
while self.requests and self.requests[0] < t - 3000:
self.requests.popleft()
return len(self.requests)import java.util.LinkedList;
import java.util.Queue;
class RecentCounter {
private Queue<Integer> requests;
public RecentCounter() {
requests = new LinkedList<>();
}
public int ping(int t) {
requests.offer(t);
while (requests.peek() < t - 3000) {
requests.poll();
}
return requests.size();
}
}#include <queue>
class RecentCounter {
private:
std::queue<int> requests;
public:
RecentCounter() {}
int ping(int t) {
requests.push(t);
while (requests.front() < t - 3000) {
requests.pop();
}
return requests.size();
}
};type RecentCounter struct {
requests []int
}
func Constructor() RecentCounter {
return RecentCounter{}
}
func (rc *RecentCounter) Ping(t int) int {
rc.requests = append(rc.requests, t)
for len(rc.requests) > 0 && rc.requests[0] < t-3000 {
rc.requests = rc.requests[1:]
}
return len(rc.requests)
}class RecentCounter
def initialize
@requests = []
end
def ping(t)
@requests.push(t)
while @requests.any? && @requests.first < t - 3000
@requests.shift
end
@requests.length
end
endTime Complexity:
O(n)
O(1)
O(n)
Medium Problems
3. Binary Tree Level Order Traversal
- Brief: Return the level order traversal of a binary tree’s nodes’ values.
- Why this pattern?: Queue naturally processes nodes level by level in BFS fashion.
- Key Insight: Track level boundaries using queue size before processing each level.
graph TD
subgraph Tree
root[3] --> left1[9]
root --> right1[20]
right1 --> left2[15]
right1 --> right2[7]
end
subgraph BFS["BFS Order"]
A[Level 0: 3] --> B[Level 1: 9, 20]
B --> C[Level 2: 15, 7]
end
style root fill:#f9f,stroke:#333
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}from collections import deque
from typing import List, Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def levelOrder(root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return resultimport java.util.*;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) { this.val = val; }
}
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(currentLevel);
}
return result;
}
}#include <vector>
#include <queue>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
std::vector<std::vector<int>> levelOrder(TreeNode* root) {
std::vector<std::vector<int>> result;
if (!root) return result;
std::queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
std::vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
TreeNode* node = q.front();
q.pop();
currentLevel.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(currentLevel);
}
return result;
}
};type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func levelOrder(root *TreeNode) [][]int {
result := [][]int{}
if root == nil {
return result
}
queue := []*TreeNode{root}
for len(queue) > 0 {
levelSize := len(queue)
currentLevel := []int{}
for i := 0; i < levelSize; i++ {
node := queue[0]
queue = queue[1:]
currentLevel = append(currentLevel, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
result = append(result, currentLevel)
}
return result
}class TreeNode
attr_accessor :val, :left, :right
def initialize(val = 0, left = nil, right = nil)
@val = val
@left = left
@right = right
end
end
def level_order(root)
result = []
return result unless root
queue = [root]
while !queue.empty?
level_size = queue.size
current_level = []
level_size.times do
node = queue.shift
current_level << node.val
queue << node.left if node.left
queue << node.right if node.right
end
result << current_level
end
result
endTime Complexity:
O(n)
O(n)
4. Open the Lock
- Brief: Find the minimum number of turns to open a 4-wheel lock, avoiding deadends.
- Why this pattern?: BFS guarantees shortest path in unweighted state space graph.
- Key Insight: Treat each lock combination as a node, generate neighbors by turning each wheel up/down.
graph TD
A["0000"] --> B["1000"]
A --> C["0001"]
A --> D["0010"]
A --> E["0100"]
B --> F["2000"]
B --> G["0000"]
function openLock(deadends, target) {
const dead = new Set(deadends);
const visited = new Set();
const queue = [['0000', 0]];
if (dead.has('0000')) return -1;
while (queue.length > 0) {
const [combination, moves] = queue.shift();
if (combination === target) return moves;
if (visited.has(combination)) continue;
if (dead.has(combination)) continue;
visited.add(combination);
for (let i = 0; i < 4; i++) {
for (const delta of [-1, 1]) {
const newDigit = String((parseInt(combination[i]) + delta + 10) % 10);
const newCombo = combination.slice(0, i) + newDigit + combination.slice(i + 1);
queue.push([newCombo, moves + 1]);
}
}
}
return -1;
}from collections import deque
def openLock(deadends: list[str], target: str) -> int:
dead = set(deadends)
visited = set()
queue = deque([("0000", 0)])
if "0000" in dead:
return -1
while queue:
combination, moves = queue.popleft()
if combination == target:
return moves
if combination in visited:
continue
if combination in dead:
continue
visited.add(combination)
for i in range(4):
for delta in [-1, 1]:
new_digit = str((int(combination[i]) + delta) % 10)
new_combo = combination[:i] + new_digit + combination[i+1:]
queue.append((new_combo, moves + 1))
return -1import java.util.*;
class Solution {
public int openLock(String[] deadends, String target) {
Set<String> dead = new HashSet<>(Arrays.asList(deadends));
Set<String> visited = new HashSet<>();
Queue<String[]> queue = new LinkedList<>();
queue.offer(new String[]{"0000", "0"});
if (dead.contains("0000")) return -1;
while (!queue.isEmpty()) {
String[] curr = queue.poll();
String combination = curr[0];
int moves = Integer.parseInt(curr[1]);
if (combination.equals(target)) return moves;
if (visited.contains(combination)) continue;
if (dead.contains(combination)) continue;
visited.add(combination);
for (int i = 0; i < 4; i++) {
for (int delta : new int[]{-1, 1}) {
int newDigit = (combination.charAt(i) - '0' + delta + 10) % 10;
String newCombo = combination.substring(0, i) + newDigit + combination.substring(i + 1);
queue.offer(new String[]{newCombo, String.valueOf(moves + 1)});
}
}
}
return -1;
}
}#include <string>
#include <vector>
#include <queue>
#include <unordered_set>
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
unordered_set<string> dead(deadends.begin(), deadends.end());
unordered_set<string> visited;
queue<pair<string, int>> q;
q.push({"0000", 0});
if (dead.count("0000")) return -1;
while (!q.empty()) {
auto [combination, moves] = q.front();
q.pop();
if (combination == target) return moves;
if (visited.count(combination)) continue;
if (dead.count(combination)) continue;
visited.insert(combination);
for (int i = 0; i < 4; i++) {
for (int delta : {-1, 1}) {
int newDigit = (combination[i] - '0' + delta + 10) % 10;
string newCombo = combination;
newCombo[i] = '0' + newDigit;
q.push({newCombo, moves + 1});
}
}
}
return -1;
}
};func openLock(deadends []string, target string) int {
dead := make(map[string]bool)
for _, d := range deadends {
dead[d] = true
}
visited := make(map[string]bool)
queue := [][2]string{{"0000", "0"}}
if dead["0000"] {
return -1
}
for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]
combination := curr[0]
moves, _ := strconv.Atoi(curr[1])
if combination == target {
return moves
}
if visited[combination] {
continue
}
if dead[combination] {
continue
}
visited[combination] = true
for i := 0; i < 4; i++ {
for _, delta := range []int{-1, 1} {
newDigit := (int(combination[i]-'0')+delta+10) % 10
newCombo := combination[:i] + string(rune('0'+newDigit)) + combination[i+1:]
queue = append(queue, [2]string{newCombo, strconv.Itoa(moves + 1)})
}
}
}
return -1
}def open_lock(deadends, target)
dead = deadends.to_set
visited = Set.new
queue = [["0000", 0]]
return -1 if dead.include?("0000")
while !queue.empty?
combination, moves = queue.shift
return moves if combination == target
next if visited.include?(combination)
next if dead.include?(combination)
visited.add(combination)
4.times do |i|
[-1, 1].each do |delta|
new_digit = (combination[i].to_i + delta) % 10
new_combo = combination[0...i] + new_digit.to_s + combination[i+1..]
queue << [new_combo, moves + 1]
end
end
end
-1
endTime Complexity:
O(1)
O(1)
5. Course Schedule II
- Brief: Find the ordering of courses to take given prerequisites.
- Why this pattern?: Kahn’s algorithm uses queue to process nodes with zero indegree one by one.
- Key Insight: Queue stores courses with no prerequisites; process them and decrement dependents’ indegree.
graph TD
subgraph Courses["Courses and Prerequisites"]
C0[0: indegree 0] -.-> C1[1]
C0 -.-> C2[2]
C1[1: indegree 1] -.-> C3[3]
C2[2: indegree 1] -.-> C3[3]
C3[3: indegree 2]
end
subgraph Queue["Processing Order"]
Q0[Queue: 0] --> Q1[Queue: 1, 2] --> Q2[Queue: 3]
end
style C0 fill:#9f9,stroke:#333
function findOrder(numCourses, prerequisites) {
const graph = Array.from({ length: numCourses }, () => []);
const indegree = new Array(numCourses).fill(0);
for (const [course, prereq] of prerequisites) {
graph[prereq].push(course);
indegree[course]++;
}
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i);
}
const result = [];
while (queue.length > 0) {
const course = queue.shift();
result.push(course);
for (const dependent of graph[course]) {
indegree[dependent]--;
if (indegree[dependent] === 0) {
queue.push(dependent);
}
}
}
return result.length === numCourses ? result : [];
}from collections import deque
from typing import List
def findOrder(numCourses: int, prerequisites: List[List[int]]) -> List[int]:
graph = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for course, prereq in prerequisites:
graph[prereq].append(course)
indegree[course] += 1
queue = deque([i for i in range(numCourses) if indegree[i] == 0])
result = []
while queue:
course = queue.popleft()
result.append(course)
for dependent in graph[course]:
indegree[dependent] -= 1
if indegree[dependent] == 0:
queue.append(dependent)
return result if len(result) == numCourses else []import java.util.*;
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
int[] indegree = new int[numCourses];
for (int[] prereq : prerequisites) {
graph.get(prereq[1]).add(prereq[0]);
indegree[prereq[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
queue.offer(i);
}
}
int[] result = new int[numCourses];
int index = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
result[index++] = course;
for (int dependent : graph.get(course)) {
indegree[dependent]--;
if (indegree[dependent] == 0) {
queue.offer(dependent);
}
}
}
return index == numCourses ? result : new int[0];
}
}#include <vector>
#include <queue>
#include <unordered_map>
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses);
vector<int> indegree(numCourses, 0);
for (auto& prereq : prerequisites) {
graph[prereq[1]].push_back(prereq[0]);
indegree[prereq[0]]++;
}
queue<int> q;
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
vector<int> result;
while (!q.empty()) {
int course = q.front();
q.pop();
result.push_back(course);
for (int dependent : graph[course]) {
indegree[dependent]--;
if (indegree[dependent] == 0) {
q.push(dependent);
}
}
}
return result.size() == numCourses ? result : vector<int>();
}
};func findOrder(numCourses int, prerequisites [][]int) []int {
graph := make([][]int, numCourses)
indegree := make([]int, numCourses)
for _, prereq := range prerequisites {
graph[prereq[1]] = append(graph[prereq[1]], prereq[0])
indegree[prereq[0]]++
}
queue := []int{}
for i := 0; i < numCourses; i++ {
if indegree[i] == 0 {
queue = append(queue, i)
}
}
result := []int{}
for len(queue) > 0 {
course := queue[0]
queue = queue[1:]
result = append(result, course)
for _, dependent := range graph[course] {
indegree[dependent]--
if indegree[dependent] == 0 {
queue = append(queue, dependent)
}
}
}
if len(result) == numCourses {
return result
}
return []int{}
}def find_order(num_courses, prerequisites)
graph = Array.new(num_courses) { [] }
indegree = Array.new(num_courses, 0)
prerequisites.each do |course, prereq|
graph[prereq] << course
indegree[course] += 1
end
queue = (0...num_courses).select { |i| indegree[i] == 0 }
result = []
until queue.empty?
course = queue.shift
result << course
graph[course].each do |dependent|
indegree[dependent] -= 1
queue << dependent if indegree[dependent] == 0
end
end
result.length == num_courses ? result : []
endTime Complexity:
O(V + E)
O(V + E)
Hard Problems
6. Sliding Window Maximum
- Brief: Find maximum element in each sliding window of size k.
- Why this pattern?: Monotonic deque maintains decreasing order, giving O(1) access to max.
- Key Insight: Store indices in deque; remove out-of-window indices and smaller elements from back.
sequenceDiagram
participant D as Deque
participant W as Window
W->>D: Process 3<br/>Deque: [0]
W->>D: Process 1<br/>Deque: [0]
W->>D: Process 3<br/>Deque: [0, 2]
W->>D: Window complete<br/>Max: nums[0] = 3
W->>D: Process -2<br/>Deque: [0, 2, 3]
function maxSlidingWindow(nums, k) {
const result = [];
const deque = [];
for (let i = 0; i < nums.length; i++) {
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i]) {
deque.pop();
}
deque.push(i);
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
}from collections import deque
from typing import List
def maxSlidingWindow(nums: List[int], k: int) -> List[int]:
result = []
dq = deque()
for i, num in enumerate(nums):
while dq and dq[0] < i - k + 1:
dq.popleft()
while dq and nums[dq[-1]] <= num:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
return resultimport java.util.*;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}#include <vector>
#include <deque>
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
deque<int> dq;
for (int i = 0; i < nums.size(); i++) {
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
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;
}
};func maxSlidingWindow(nums []int, k int) []int {
result := []int{}
dq := []int{}
for i, num := range nums {
for len(dq) > 0 && dq[0] < i-k+1 {
dq = dq[1:]
}
for len(dq) > 0 && nums[dq[len(dq)-1]] <= num {
dq = dq[:len(dq)-1]
}
dq = append(dq, i)
if i >= k-1 {
result = append(result, nums[dq[0]])
}
}
return result
}def max_sliding_window(nums, k)
result = []
dq = []
nums.each_with_index do |num, i|
while !dq.empty? && dq.first < i - k + 1
dq.shift
end
while !dq.empty? && nums[dq.last] <= num
dq.pop
end
dq.push(i)
if i >= k - 1
result << nums[dq.first]
end
end
result
endTime Complexity:
O(n)
O(k)
Recommended Study Order
- Start with Implement Queue using Stacks to understand queue mechanics and LIFO to FIFO conversion.
- Move to Binary Tree Level Order Traversal to master the classic BFS queue pattern.
- Tackle Open the Lock to practice BFS on state spaces with visited tracking.
- Progress to Sliding Window Maximum for advanced monotonic deque technique.
- Finish with Course Schedule II to understand topological sorting with queue-based Kahn’s algorithm.
Pro Tip: When implementing BFS, always mark nodes as visited before adding them to the queue, not after removing them. This prevents the same node from being added multiple times and causing exponential memory usage.