Skip to content
DSA stacks queues 12 min read

Stacks & Queues Exercises: Answer Sheet & Solutions

This is the answer sheet for the stacks & queues exercises. Each solution gives the key idea, the optimal complexity, the code in all four languages (use the switcher), and common pitfalls. Try the problems first — then check your work here.

Q1. Reverse a string using a stack

Idea: A stack is LIFO, so pushing every character and then popping them yields the characters in reverse order — the reversal is automatic. (In practice you’d just reverse in place, but this demonstrates the LIFO property.)

Complexity: O(n) time, O(n) space.

#include <string>
#include <stack>
std::string reverseWithStack(const std::string& s) {
    std::stack<char> st;
    for (char c : s) st.push(c);
    std::string out;
    while (!st.empty()) { out += st.top(); st.pop(); }
    return out;
}

Pitfall: Building the result with repeated string concatenation is O(n²) in some languages. Use a StringBuilder (Java) or a list join (Python) for true O(n).

Q2. Valid parentheses

Idea: Push every opening bracket. On a closing bracket, the top of the stack must be its matching opener — otherwise it’s invalid. At the end the stack must be empty (no unclosed openers).

Complexity: O(n) time, O(n) space.

#include <string>
#include <stack>
#include <unordered_map>
bool isValid(const std::string& s) {
    std::unordered_map<char, char> match = {{')','('}, {']','['}, {'}','{'}};
    std::stack<char> st;
    for (char c : s) {
        if (match.count(c)) {
            if (st.empty() || st.top() != match[c]) return false;
            st.pop();
        } else st.push(c);
    }
    return st.empty();
}

Pitfall: Returning true as soon as you reach the end without checking that the stack is empty. "(((" never mismatches but is still invalid.

Q3. Implement a queue using two stacks

Idea: Use an in stack for enqueues and an out stack for dequeues. To dequeue, if out is empty, pour everything from in into out — this reverses LIFO into FIFO. Each element is moved at most once, so dequeue is amortized O(1).

Complexity: enqueue O(1); dequeue amortized O(1). O(n) space.

#include <stack>
#include <stdexcept>
class MyQueue {
    std::stack<int> in, out;
public:
    void enqueue(int x) { in.push(x); }
    int dequeue() {
        if (out.empty()) {
            if (in.empty()) throw std::out_of_range("empty queue");
            while (!in.empty()) { out.push(in.top()); in.pop(); }
        }
        int x = out.top(); out.pop();
        return x;
    }
};

Pitfall: Refilling out from in on every dequeue (even when out is non-empty) breaks FIFO order and ruins the amortized bound. Only refill when out is empty.

Q4. Min Stack

Idea: Alongside each value, store the minimum of the stack at that point. Pushing records min(x, currentMin); popping discards both. The current minimum is always the top of the min-tracking stack — O(1).

Complexity: O(1) for every operation. O(n) space.

#include <stack>
#include <algorithm>
class MinStack {
    std::stack<int> data, mins;
public:
    void push(int x) {
        data.push(x);
        mins.push(mins.empty() ? x : std::min(x, mins.top()));
    }
    void pop() { data.pop(); mins.pop(); }
    int top() const { return data.top(); }
    int getMin() const { return mins.top(); }
};

Pitfall: Keeping a single min variable instead of a stack. When you pop the current minimum you’d have no way to restore the previous minimum in O(1). The parallel min-stack remembers every prefix minimum.

Q5. Next Greater Element

Idea: Use a monotonic stack of indices whose values are decreasing. Scanning left to right, when the current value exceeds the value at the top index, the current value is that index’s next-greater — pop and record it. Then push the current index.

Complexity: O(n) time (each index pushed/popped once), O(n) space.

#include <vector>
#include <stack>
std::vector<int> nextGreater(const std::vector<int>& a) {
    int n = a.size();
    std::vector<int> res(n, -1);
    std::stack<int> st;
    for (int i = 0; i < n; i++) {
        while (!st.empty() && a[i] > a[st.top()]) {
            res[st.top()] = a[i];
            st.pop();
        }
        st.push(i);
    }
    return res;
}

Pitfall: Pushing values instead of indices. You need the index to write the answer into the right slot of res. Store indices, read values via a[index].

Q6. Implement a stack using two queues

Idea: Make push do the work. Enqueue the new element into the empty helper queue q2, then move every element of q1 behind it, and swap the two queues. Now q1’s front is always the most recently pushed element, so pop is a plain O(1) dequeue.

Complexity: push O(n); pop, top O(1). O(n) space.

#include <queue>
#include <stdexcept>
class MyStack {
    std::queue<int> q1, q2;
public:
    void push(int x) {
        q2.push(x);
        while (!q1.empty()) { q2.push(q1.front()); q1.pop(); }
        std::swap(q1, q2);
    }
    int pop() {
        if (q1.empty()) throw std::out_of_range("empty stack");
        int x = q1.front(); q1.pop();
        return x;
    }
    int top() const { return q1.front(); }
};

Pitfall: Forgetting the swap, or enqueuing the new element into q1 first — then the rotation order is wrong and you no longer get LIFO. Always stage the new element in the empty queue, drain the other behind it, then swap.

Q7. Sliding window maximum

Idea: Keep a monotonic deque of indices with decreasing values. For each i: pop indices off the back whose values are <= a[i] (they can never be a max while a[i] is in the window), then push i. Drop the front if it’s outside the window (< i - k + 1). The front is always the current window’s maximum.

Complexity: O(n) time (each index enters/leaves the deque once), O(k) space.

#include <vector>
#include <deque>
std::vector<int> maxSlidingWindow(const std::vector<int>& a, int k) {
    std::deque<int> dq;            // indices, values decreasing
    std::vector<int> res;
    for (int i = 0; i < (int)a.size(); i++) {
        while (!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();
        dq.push_back(i);
        if (dq.front() <= i - k) dq.pop_front();   // out of window
        if (i >= k - 1) res.push_back(a[dq.front()]);
    }
    return res;
}

Pitfall: Storing values instead of indices — you can’t tell when the front has slid out of the window. Track indices and evict the front when front <= i - k.


That’s the full answer sheet. Back to the stacks & queues exercises, or continue to hashing.

Last updated June 25, 2026
Was this helpful?