Stack

Stack #

A Stack is a linear data structure that follows the LIFO (Last In, First Out) principle, where the last element added is the first one to be removed. Stacks have two primary operations: push, which adds an element to the top, and pop, which removes the top element.

There are three common ways to implement a stack:

  • Using ArrayList (dynamic array).
  • Using a Linked List (dynamic structure of nodes).
  • Using a Resizing Array (dynamic array with resizing capabilities).

Stack Implementations #

Stack as ArrayList #

public class StackAsArrayList {
    ArrayList<Integer> stack = new ArrayList<>();

    public boolean push(int value) {
        return stack.add(value);  // Adds an element to the end of the list
    }

    public int pop() {
        return stack.remove(stack.size() - 1);  // Removes the last element
    }

    public int peek() {
        return stack.get(stack.size() - 1);  // Returns the last element without removing
    }

    public int size() {
        return stack.size();  // Returns the size of the stack
    }
}

Stack as Linked List #

public class StackAsLinkedList {
    Node top;
    int size;

    public class Node {
        int value;
        Node next;

        public Node(int value) {
            this.value = value;
        }
    }

    public void push(int value) {
        Node newNode = new Node(value);  // Creates a new node
        newNode.next = top;  // Sets the next reference to the current top
        top = newNode;  // Updates the top to the new node
        size++;
    }

    public Node pop() {
        if (size == 0) return null;  // Stack underflow check
        Node popped = top;  // Get the top element
        top = top.next;  // Update top to the next element
        size--;
        return popped;
    }

    public Node peek() {
        return top;  // Returns the top node without removing
    }

    public int size() {
        return size;  // Returns the current size
    }
}

Stack as Resizing Array #

public class StackAsResizingArray {
    int[] stack = new int[1];  // Start with an initial capacity of 1
    int size = 0;

    public void push(int value) {
        if (isFull()) resize();  // Resize the array if it's full
        stack[size++] = value;  // Add the value and increment size
    }

    public int pop() {
        if (size == 0) throw new NoSuchElementException("Stack is empty");
        return stack[--size];  // Decrement size and return the popped element
    }

    public int peek() {
        if (size == 0) throw new NoSuchElementException("Stack is empty");
        return stack[size - 1];  // Return the top element without removing
    }

    public int size() {
        return size;  // Return the current size
    }

    private boolean isFull() {
        return size == stack.length;  // Check if the stack is full
    }

    private void resize() {
        stack = Arrays.copyOf(stack, stack.length * 2);  // Double the array size
    }
}

Operations, Time, and Space Complexities #

OperationArrayList StackLinkedList StackResizing Array StackTime ComplexitySpace Complexity
push()O(1)O(1)O(1) (amortized)O(1)O(N)
pop()O(1)O(1)O(1)O(1)O(N)
peek()O(1)O(1)O(1)O(1)O(1)
size()O(1)O(1)O(1)O(1)O(1)
resize()N/AN/AO(N)O(N)O(N)
  • ArrayList Stack: Uses dynamic arrays, efficient with most operations, but resizing can be costly.
  • LinkedList Stack: Uses nodes and is flexible with dynamic memory usage but has higher memory overhead due to the node structure.
  • Resizing Array Stack: Combines dynamic array properties with resizing to ensure efficient use of space.

When to Use a Stack #

Stacks are useful in scenarios where you need to maintain a history or order of operations that follow the Last In, First Out (LIFO) principle. Some common uses of stacks include:

  • Expression evaluation (e.g., postfix evaluation).
  • Backtracking problems (e.g., solving mazes).
  • Function call management (e.g., recursion).
  • Undo operations in text editors or browsers.

Advantages and Disadvantages #

ImplementationAdvantagesDisadvantages
ArrayList StackDynamic resizing; O(1) access time.Resizing can be costly (O(N)).
LinkedList StackEfficient memory use for dynamic data.Higher memory overhead for pointers.
Resizing ArrayAmortized constant time for most operations.Resizing incurs time overhead (O(N)).

Leetcode #

LevelProblem Name & LinkTechnique Used
🟢 Easy20. Valid ParenthesesStack (LIFO)
🟢 Easy155. Min StackStack with Auxiliary Min Tracking
🟢 Easy232. Implement Queue using StacksTwo Stacks
🟢 Easy682. Baseball GameStack for Score Calculation
🟢 Easy844. Backspace String CompareStack for String Processing
🟡 Medium71. Simplify PathStack for Path Navigation
🟡 Medium150. Evaluate Reverse Polish NotationStack for Expression Evaluation
🟡 Medium394. Decode StringStack for Nested Processing
🟡 Medium739. Daily TemperaturesMonotonic Stack
🟡 Medium901. Online Stock SpanMonotonic Stack
🟡 Medium1021. Remove Outermost ParenthesesStack for Parentheses Tracking
🟡 Medium1047. Remove All Adjacent Duplicates In StringStack for Character Removal
🟡 Medium739. Daily TemperaturesMonotonic Stack
🔴 Hard84. Largest Rectangle in HistogramMonotonic Stack
🔴 Hard85. Maximal RectangleStack for 2D Histogram
🔴 Hard316. Remove Duplicate LettersStack + Greedy
🔴 Hard895. Maximum Frequency StackStack with Frequency Map
🔴 Hard42. Trapping Rain WaterStack for Water Collection