Stacks and Queues - (Theory)

Stack Basics

A stack is a LIFO (Last In First Out) data structure. Key operations:

Java Implementation:

import java.util.ArrayDeque;
import java.util.Deque;

public class StackBasics {
    public static void main(String[] args) {
        Deque<Integer> stack = new ArrayDeque<>();
        
        // Push elements
        stack.push(10);
        stack.push(20);
        stack.push(30);
        
        System.out.println("Top element: " + stack.peek()); // 30
        
        // Pop elements
        System.out.println(stack.pop()); // 30
        System.out.println(stack.pop()); // 20
        
        // Check empty
        System.out.println("Is empty? " + stack.isEmpty()); // false
    }
}

Stack Patterns

1. Next Greater Element to Right (NGR)

For each element, find the next greater element to its right. If none, return -1.

Example:
Input: [4, 5, 2, 10] → Output: [5, 10, 10, -1]

Approach:

Java Code:

public int[] nextGreaterRight(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = n - 1; i >= 0; i--) {
        while (!stack.isEmpty() && stack.peek() <= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return res;
}

TC: O(n), SC: O(n)


2. Next Greater Element to Left (NGL)

For each element, find the next greater element to its left. If none, return -1.

Example:
Input: [4, 5, 2, 10] → Output: [-1, -1, 5, -1]

Java Code:

public int[] nextGreaterLeft(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && stack.peek() <= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return res;
}

TC: O(n), SC: O(n)


3. Next Smaller Element to Right (NSR)

For each element, find the next smaller element to its right. If none, return -1.

Example:
Input: [4, 5, 2, 10] → Output: [2, 2, -1, -1]

Java Code:

public int[] nextSmallerRight(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = n - 1; i >= 0; i--) {
        while (!stack.isEmpty() && stack.peek() >= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return res;
}

TC: O(n), SC: O(n)


4. Next Smaller Element to Left (NSL)

For each element, find the next smaller element to its left. If none, return -1.

Example:
Input: [4, 5, 2, 10] → Output: [-1, 4, -1, 2]

Java Code:

public int[] nextSmallerLeft(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && stack.peek() >= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return res;
}

TC: O(n), SC: O(n)


5. Stock Span Problem

Calculate the span of stock prices (days with price ≤ current price including current).

Example:
Input: [100, 80, 60, 70, 60, 75, 85] → Output: [1, 1, 1, 2, 1, 4, 6]

Java Code:

public int[] stockSpan(int[] prices) {
    int n = prices.length;
    int[] span = new int[n];
    Deque<Integer> stack = new ArrayDeque<>(); // Store indices
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && prices[stack.peek()] <= prices[i]) {
            stack.pop();
        }
        span[i] = stack.isEmpty() ? i + 1 : i - stack.peek();
        stack.push(i);
    }
    return span;
}

TC: O(n), SC: O(n)


6. Maximum Area Histogram (MAH)

Find the largest rectangle area under a histogram.

Approach:

Java Code:

public int maxAreaHistogram(int[] heights) {
    int n = heights.length;
    int[] left = nextSmallerLeftIndex(heights);
    int[] right = nextSmallerRightIndex(heights);
    int maxArea = 0;
    
    for (int i = 0; i < n; i++) {
        int width = right[i] - left[i] - 1;
        maxArea = Math.max(maxArea, heights[i] * width);
    }
    return maxArea;
}

private int[] nextSmallerLeftIndex(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(i);
    }
    return res;
}

private int[] nextSmallerRightIndex(int[] arr) {
    int n = arr.length;
    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    
    for (int i = n - 1; i >= 0; i--) {
        while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
            stack.pop();
        }
        res[i] = stack.isEmpty() ? n : stack.peek();
        stack.push(i);
    }
    return res;
}

TC: O(n), SC: O(n)


7. Expression Evaluation

a. Infix to Postfix

Convert infix expression to postfix.

Java Code:

public String infixToPostfix(String exp) {
    StringBuilder sb = new StringBuilder();
    Deque<Character> stack = new ArrayDeque<>();
    String precedence = "+-*/^";
    String associativity = "LLLLRL"; // L: Left, R: Right
    
    for (char c : exp.toCharArray()) {
        if (Character.isLetterOrDigit(c)) sb.append(c);
        else if (c == '(') stack.push(c);
        else if (c == ')') {
            while (!stack.isEmpty() && stack.peek() != '(') 
                sb.append(stack.pop());
            stack.pop(); // Remove '('
        } else {
            while (!stack.isEmpty() && 
                   (precedence.indexOf(stack.peek()) > precedence.indexOf(c) || 
                   (precedence.indexOf(stack.peek()) == precedence.indexOf(c) && 
                    associativity.charAt(precedence.indexOf(c)) == 'L')) {
                sb.append(stack.pop());
            }
            stack.push(c);
        }
    }
    while (!stack.isEmpty()) sb.append(stack.pop());
    return sb.toString();
}
b. Postfix Evaluation

Evaluate postfix expression.

Java Code:

public int evalPostfix(String postfix) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (char c : postfix.toCharArray()) {
        if (Character.isDigit(c)) stack.push(c - '0');
        else {
            int b = stack.pop(), a = stack.pop();
            switch(c) {
                case '+': stack.push(a + b); break;
                case '-': stack.push(a - b); break;
                case '*': stack.push(a * b); break;
                case '/': stack.push(a / b); break;
                case '^': stack.push((int)Math.pow(a, b)); break;
            }
        }
    }
    return stack.pop();
}

TC: O(n) for both, SC: O(n)


8. Balanced Parentheses

Check if parentheses are balanced.

Java Code:

public boolean isBalanced(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') stack.push(c);
        else if (!stack.isEmpty() && 
                ((c == ')' && stack.peek() == '(') ||
                 (c == ']' && stack.peek() == '[') ||
                 (c == '}' && stack.peek() == '{'))) {
            stack.pop();
        } else return false;
    }
    return stack.isEmpty();
}

TC: O(n), SC: O(n)


9. Redundant Brackets

Check if expression has redundant brackets.

Java Code:

public boolean hasRedundant(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c != ')') stack.push(c);
        else {
            boolean hasOperator = false;
            while (!stack.isEmpty() && stack.peek() != '(') {
                char top = stack.pop();
                if ("+-*/".indexOf(top) != -1) hasOperator = true;
            }
            if (!hasOperator) return true;
            stack.pop(); // Remove '('
        }
    }
    return false;
}

TC: O(n), SC: O(n)


10. Min Stack

Design a stack that supports getMin() in O(1) time.

Java Code:

class MinStack {
    private Deque<Integer> stack = new ArrayDeque<>();
    private Deque<Integer> minStack = new ArrayDeque<>();
    
    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || val <= minStack.peek()) 
            minStack.push(val);
    }
    
    public void pop() {
        if (stack.pop().equals(minStack.peek())) 
            minStack.pop();
    }
    
    public int top() { return stack.peek(); }
    public int getMin() { return minStack.peek(); }
}

TC: O(1) for all operations, SC: O(n)


Key Takeaways

Queue and Common Patterns

Queue Basics

A queue is a FIFO (First In First Out) data structure. Key operations:

Java Implementation:

import java.util.LinkedList;
import java.util.Queue;

public class QueueBasics {
    public static void main(String[] args) {
        Queue<Integer> q = new LinkedList<>();
        
        // Enqueue elements
        q.add(10);
        q.add(20);
        q.add(30);
        
        System.out.println("Front element: " + q.peek()); // 10
        
        // Dequeue elements
        System.out.println(q.remove()); // 10
        System.out.println(q.remove()); // 20
        
        System.out.println("Is empty? " + q.isEmpty()); // false
    }
}

Queue Patterns

1. Sliding Window Maximum

Find maximum in each sliding window of size k.

Example:
Input: [1,3,-1,-3,5,3,6,7], k=3 → Output: [3,3,5,5,6,7]

Approach:

Java Code:

public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums == null || k <= 0) return new int[0];
    int n = nums.length;
    int[] result = new int[n - k + 1];
    Deque<Integer> dq = new ArrayDeque<>();
    
    for (int i = 0; i < n; i++) {
        // Remove out-of-window elements
        while (!dq.isEmpty() && dq.peekFirst() < i - k + 1) {
            dq.pollFirst();
        }
        
        // Remove smaller elements from rear
        while (!dq.isEmpty() && nums[dq.peekLast()] < nums[i]) {
            dq.pollLast();
        }
        
        dq.offerLast(i);
        if (i >= k - 1) {
            result[i - k + 1] = nums[dq.peekFirst()];
        }
    }
    return result;
}

TC: O(n), SC: O(k)


2. First Non-Repeating Character in Stream

Find first non-repeating character for each position in stream.

Example:
Input: "aabc" → Output: "a#bb"

Java Code:

public String firstNonRepeating(String stream) {
    int[] freq = new int[26];
    Queue<Character> q = new LinkedList<>();
    StringBuilder sb = new StringBuilder();
    
    for (char ch : stream.toCharArray()) {
        freq[ch - 'a']++;
        q.offer(ch);
        
        while (!q.isEmpty() && freq[q.peek() - 'a'] > 1) {
            q.poll();
        }
        
        sb.append(q.isEmpty() ? '#' : q.peek());
    }
    return sb.toString();
}

TC: O(n), SC: O(1) (fixed character set)


3. Rotten Oranges (BFS with Queue)

Calculate minimum time to rot all oranges.

Approach:

Java Code:

public int orangesRotting(int[][] grid) {
    int m = grid.length, n = grid[0].length;
    Queue<int[]> q = new LinkedList<>();
    int fresh = 0, time = 0;
    int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
    
    // Initialize queue and count fresh oranges
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 2) q.offer(new int[]{i, j, 0});
            else if (grid[i][j] == 1) fresh++;
        }
    }
    
    // BFS processing
    while (!q.isEmpty()) {
        int[] cell = q.poll();
        time = Math.max(time, cell[2]);
        
        for (int[] d : dirs) {
            int x = cell[0] + d[0], y = cell[1] + d[1];
            if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1) {
                grid[x][y] = 2;
                fresh--;
                q.offer(new int[]{x, y, cell[2] + 1});
            }
        }
    }
    return fresh == 0 ? time : -1;
}

TC: O(mn), SC: O(mn)


4. Circular Queue Implementation

Fixed-size FIFO structure with efficient wraparound.

Java Code:

class MyCircularQueue {
    private int[] data;
    private int front, rear, size, capacity;
    
    public MyCircularQueue(int k) {
        data = new int[k];
        capacity = k;
        front = 0; rear = -1; size = 0;
    }
    
    public boolean enQueue(int val) {
        if (isFull()) return false;
        rear = (rear + 1) % capacity;
        data[rear] = val;
        size++;
        return true;
    }
    
    public boolean deQueue() {
        if (isEmpty()) return false;
        front = (front + 1) % capacity;
        size--;
        return true;
    }
    
    public int Front() {
        return isEmpty() ? -1 : data[front];
    }
    
    public int Rear() {
        return isEmpty() ? -1 : data[rear];
    }
    
    public boolean isEmpty() {
        return size == 0;
    }
    
    public boolean isFull() {
        return size == capacity;
    }
}

TC: O(1) all operations, SC: O(n)


5. Reverse a Queue

Reverse elements in a queue.

Java Code:

public void reverseQueue(Queue<Integer> q) {
    Stack<Integer> s = new Stack<>();
    while (!q.isEmpty()) s.push(q.poll());
    while (!s.isEmpty()) q.offer(s.pop());
}

TC: O(n), SC: O(n)


6. Generate Binary Numbers 1 to n

Generate binary numbers from 1 to n using queue.

Example:
n=3 → ["1", "10", "11"]

Java Code:

public List<String> generateBinary(int n) {
    Queue<String> q = new LinkedList<>();
    List<String> result = new ArrayList<>();
    q.offer("1");
    
    while (n-- > 0) {
        String s = q.poll();
        result.add(s);
        q.offer(s + "0");
        q.offer(s + "1");
    }
    return result;
}

TC: O(n), SC: O(n)


7. Implement Stack using Queues

LIFO using two queues.

Approach:

Java Code:

class MyStack {
    private Queue<Integer> q1 = new LinkedList<>();
    private Queue<Integer> q2 = new LinkedList<>();
    
    public void push(int x) {
        q2.offer(x);
        while (!q1.isEmpty()) q2.offer(q1.poll());
        Queue<Integer> temp = q1;
        q1 = q2;
        q2 = temp;
    }
    
    public int pop() {
        return q1.poll();
    }
    
    public int top() {
        return q1.peek();
    }
    
    public boolean empty() {
        return q1.isEmpty();
    }
}

TC: Push O(n), Pop O(1), SC: O(n)


8. Implement Queue using Stacks

FIFO using two stacks.

Approach:

Java Code:

class MyQueue {
    private Stack<Integer> in = new Stack<>();
    private Stack<Integer> out = new Stack<>();
    
    public void push(int x) {
        in.push(x);
    }
    
    public int pop() {
        if (out.isEmpty()) transfer();
        return out.pop();
    }
    
    public int peek() {
        if (out.isEmpty()) transfer();
        return out.peek();
    }
    
    private void transfer() {
        while (!in.isEmpty()) out.push(in.pop());
    }
    
    public boolean empty() {
        return in.isEmpty() && out.isEmpty();
    }
}

TC: Amortized O(1) for all operations, SC: O(n)


9. Priority Queue Patterns

a. Kth Largest Element
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>();
    for (int num : nums) {
        pq.offer(num);
        if (pq.size() > k) pq.poll();
    }
    return pq.peek();
}

TC: O(n log k), SC: O(k)

b. Merge K Sorted Lists
public ListNode mergeKLists(ListNode[] lists) {
    PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
    for (ListNode node : lists) 
        if (node != null) pq.offer(node);
    
    ListNode dummy = new ListNode(0);
    ListNode curr = dummy;
    while (!pq.isEmpty()) {
        curr.next = pq.poll();
        curr = curr.next;
        if (curr.next != null) pq.offer(curr.next);
    }
    return dummy.next;
}

TC: O(n log k), SC: O(k)


Key Takeaways