Stack Data Structure in Python – DSA

    Table of Contents

    1. Introduction
    2. Stack Fundamentals
    3. Implementation Methods
    4. Operations and Time Complexity
    5. Practical Applications
    6. Common Problems
    7. Advanced Topics

    Introduction

    A Stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. The last element added to the stack will be the first one to be removed. Think of it like a stack of plates – you can only add or remove plates from the top.

    Real-World Analogies

    • Stack of plates in a cafeteria
    • Browser back button (navigation history)
    • Undo mechanism in text editors
    • Call stack in programming

    Key Characteristics

    • LIFO: Last In, First Out
    • Restricted Access: Elements can only be added or removed from one end (top)
    • Dynamic Size: Can grow or shrink as needed

    Stack Fundamentals

    Basic Operations

    1. Push: Add an element to the top of the stack
    2. Pop: Remove and return the top element
    3. Peek/Top: View the top element without removing it
    4. isEmpty: Check if the stack is empty
    5. Size: Get the number of elements in the stack

    Visual Representation

    Initial Stack:        After Push(5):      After Pop():
        |   |                |   |               |   |
        | 3 | ← Top          | 5 | ← Top         | 3 | ← Top
        | 2 |                | 3 |               | 2 |
        | 1 |                | 2 |               | 1 |
        -----                | 1 |               -----
                             -----
    Python

    Implementation Methods

    Method 1: Using Python List

    Python lists provide built-in methods that make stack implementation straightforward.

    class Stack:
        """Stack implementation using Python list"""
    
        def __init__(self):
            """Initialize an empty stack"""
            self.items = []
    
        def is_empty(self):
            """Check if stack is empty"""
            return len(self.items) == 0
    
        def push(self, item):
            """Add an item to the top of stack"""
            self.items.append(item)
    
        def pop(self):
            """Remove and return the top item"""
            if self.is_empty():
                raise IndexError("pop from empty stack")
            return self.items.pop()
    
        def peek(self):
            """Return the top item without removing it"""
            if self.is_empty():
                raise IndexError("peek from empty stack")
            return self.items[-1]
    
        def size(self):
            """Return the number of items in stack"""
            return len(self.items)
    
        def __str__(self):
            """String representation of stack"""
            return f"Stack({self.items})"
    
        def __repr__(self):
            return self.__str__()
    
    
    # Example usage
    stack = Stack()
    stack.push(1)
    stack.push(2)
    stack.push(3)
    print(f"Stack: {stack}")           # Stack([1, 2, 3])
    print(f"Top element: {stack.peek()}")  # 3
    print(f"Popped: {stack.pop()}")    # 3
    print(f"Size: {stack.size()}")     # 2
    print(f"Is empty: {stack.is_empty()}")  # False
    Python

    Method 2: Using collections.deque

    The deque (double-ended queue) is more efficient for stack operations than a list.

    from collections import deque
    
    class StackDeque:
        """Stack implementation using collections.deque"""
    
        def __init__(self):
            self.items = deque()
    
        def is_empty(self):
            return len(self.items) == 0
    
        def push(self, item):
            self.items.append(item)
    
        def pop(self):
            if self.is_empty():
                raise IndexError("pop from empty stack")
            return self.items.pop()
    
        def peek(self):
            if self.is_empty():
                raise IndexError("peek from empty stack")
            return self.items[-1]
    
        def size(self):
            return len(self.items)
    
        def __str__(self):
            return f"Stack({list(self.items)})"
    
    
    # Example usage
    stack = StackDeque()
    stack.push(10)
    stack.push(20)
    stack.push(30)
    print(stack)  # Stack([10, 20, 30])
    Python

    Method 3: Using Linked List

    A linked list implementation provides O(1) operations and dynamic memory allocation.

    class Node:
        """Node for linked list"""
        def __init__(self, data):
            self.data = data
            self.next = None
    
    
    class StackLinkedList:
        """Stack implementation using Linked List"""
    
        def __init__(self):
            self.top = None
            self._size = 0
    
        def is_empty(self):
            return self.top is None
    
        def push(self, item):
            """Add item to top of stack"""
            new_node = Node(item)
            new_node.next = self.top
            self.top = new_node
            self._size += 1
    
        def pop(self):
            """Remove and return top item"""
            if self.is_empty():
                raise IndexError("pop from empty stack")
    
            popped_data = self.top.data
            self.top = self.top.next
            self._size -= 1
            return popped_data
    
        def peek(self):
            """Return top item without removing"""
            if self.is_empty():
                raise IndexError("peek from empty stack")
            return self.top.data
    
        def size(self):
            return self._size
    
        def __str__(self):
            """String representation (top to bottom)"""
            items = []
            current = self.top
            while current:
                items.append(current.data)
                current = current.next
            return f"Stack({items})"
    
    
    # Example usage
    stack = StackLinkedList()
    stack.push('A')
    stack.push('B')
    stack.push('C')
    print(stack)  # Stack(['C', 'B', 'A'])
    print(stack.pop())  # C
    Python

    Method 4: Using queue.LifoQueue (Thread-safe)

    For multi-threaded applications, use the thread-safe LifoQueue.

    from queue import LifoQueue
    
    # Create a stack with maximum size
    stack = LifoQueue(maxsize=5)
    
    # Push items
    stack.put(1)
    stack.put(2)
    stack.put(3)
    
    # Pop items
    print(stack.get())  # 3
    print(stack.get())  # 2
    
    # Check if empty
    print(stack.empty())  # False
    
    # Check if full
    print(stack.full())   # False
    
    # Get size
    print(stack.qsize())  # 1
    Python

    Operations and Time Complexity

    Time Complexity Analysis

    OperationListDequeLinked ListLifoQueue
    PushO(1)*O(1)O(1)O(1)
    PopO(1)O(1)O(1)O(1)
    PeekO(1)O(1)O(1)N/A
    isEmptyO(1)O(1)O(1)O(1)
    SizeO(1)O(1)O(1)O(1)

    *Amortized O(1) – occasionally O(n) when list needs to resize

    Space Complexity

    • All implementations: O(n) where n is the number of elements

    Comparison of Implementation Methods

    import time
    from collections import deque
    
    def benchmark_stack_operations(n=100000):
        """Compare performance of different stack implementations"""
    
        # List-based stack
        start = time.time()
        list_stack = []
        for i in range(n):
            list_stack.append(i)
        for i in range(n):
            list_stack.pop()
        list_time = time.time() - start
    
        # Deque-based stack
        start = time.time()
        deque_stack = deque()
        for i in range(n):
            deque_stack.append(i)
        for i in range(n):
            deque_stack.pop()
        deque_time = time.time() - start
    
        print(f"List-based: {list_time:.4f} seconds")
        print(f"Deque-based: {deque_time:.4f} seconds")
        print(f"Deque is {list_time/deque_time:.2f}x faster")
    
    # Run benchmark
    benchmark_stack_operations()
    Python

    Practical Applications

    1. Balanced Parentheses Checker

    Check if parentheses, brackets, and braces are balanced in an expression.

    def is_balanced(expression):
        """
        Check if parentheses are balanced
    
        Examples:
            is_balanced("()[]{}") → True
            is_balanced("([)]") → False
            is_balanced("{[()]}") → True
        """
        stack = []
        opening = "([{"
        closing = ")]}"
        pairs = {"(": ")", "[": "]", "{": "}"}
    
        for char in expression:
            if char in opening:
                stack.append(char)
            elif char in closing:
                if not stack:
                    return False
                if pairs[stack.pop()] != char:
                    return False
    
        return len(stack) == 0
    
    
    # Test cases
    test_cases = [
        "()[]{}",
        "([)]",
        "{[()]}",
        "(((",
        "())",
        "{[(])}",
        ""
    ]
    
    for expr in test_cases:
        print(f"{expr:15}{is_balanced(expr)}")
    Python

    Output:

    ()[]{}True
    ([)]False
    {[()]}True
    (((False
    ())False
    {[(])}          → False
    True
    Python

    2. Reverse a String

    def reverse_string(s):
        """Reverse a string using stack"""
        stack = []
    
        # Push all characters to stack
        for char in s:
            stack.append(char)
    
        # Pop all characters to build reversed string
        reversed_str = ""
        while stack:
            reversed_str += stack.pop()
    
        return reversed_str
    
    
    # Alternative: Pythonic one-liner
    def reverse_string_pythonic(s):
        return s[::-1]
    
    
    # Test
    text = "Hello, World!"
    print(f"Original: {text}")
    print(f"Reversed: {reverse_string(text)}")
    Python

    3. Infix to Postfix Conversion

    Convert mathematical expressions from infix to postfix notation.

    def infix_to_postfix(expression):
        """
        Convert infix expression to postfix
    
        Example: "A+B*C" → "ABC*+"
        """
        precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3}
        stack = []
        output = []
    
        for char in expression:
            # Operand
            if char.isalnum():
                output.append(char)
    
            # Left parenthesis
            elif char == '(':
                stack.append(char)
    
            # Right parenthesis
            elif char == ')':
                while stack and stack[-1] != '(':
                    output.append(stack.pop())
                stack.pop()  # Remove '('
    
            # Operator
            else:
                while (stack and stack[-1] != '(' and
                       precedence.get(stack[-1], 0) >= precedence.get(char, 0)):
                    output.append(stack.pop())
                stack.append(char)
    
        # Pop remaining operators
        while stack:
            output.append(stack.pop())
    
        return ''.join(output)
    
    
    # Test cases
    expressions = [
        "A+B*C",
        "A*(B+C)",
        "A+B*C-D/E",
        "(A+B)*(C-D)"
    ]
    
    for expr in expressions:
        print(f"{expr:20}{infix_to_postfix(expr)}")
    Python

    4. Evaluate Postfix Expression

    def evaluate_postfix(expression):
        """
        Evaluate a postfix expression
    
        Example: "23*5+" → 11  (2*3 + 5)
        """
        stack = []
    
        for char in expression:
            if char.isdigit():
                stack.append(int(char))
            else:
                # Pop two operands
                operand2 = stack.pop()
                operand1 = stack.pop()
    
                # Perform operation
                if char == '+':
                    result = operand1 + operand2
                elif char == '-':
                    result = operand1 - operand2
                elif char == '*':
                    result = operand1 * operand2
                elif char == '/':
                    result = operand1 / operand2
    
                stack.append(result)
    
        return stack.pop()
    
    
    # Test
    expressions = [
        ("23*5+", "2*3 + 5"),
        ("53+2*", "(5+3) * 2"),
        ("234*+5-", "2 + 3*4 - 5")
    ]
    
    for postfix, infix in expressions:
        result = evaluate_postfix(postfix)
        print(f"{postfix:10} ({infix:15}) = {result}")
    Python

    5. Browser History Implementation

    class BrowserHistory:
        """Simulate browser navigation with back/forward buttons"""
    
        def __init__(self, homepage):
            self.back_stack = []
            self.forward_stack = []
            self.current = homepage
    
        def visit(self, url):
            """Visit a new URL"""
            self.back_stack.append(self.current)
            self.current = url
            self.forward_stack.clear()  # Clear forward history
            print(f"Visiting: {url}")
    
        def back(self):
            """Go back to previous page"""
            if not self.back_stack:
                print("No pages in back history")
                return self.current
    
            self.forward_stack.append(self.current)
            self.current = self.back_stack.pop()
            print(f"Back to: {self.current}")
            return self.current
    
        def forward(self):
            """Go forward to next page"""
            if not self.forward_stack:
                print("No pages in forward history")
                return self.current
    
            self.back_stack.append(self.current)
            self.current = self.forward_stack.pop()
            print(f"Forward to: {self.current}")
            return self.current
    
        def current_page(self):
            """Get current page"""
            return self.current
    
    
    # Example usage
    browser = BrowserHistory("google.com")
    browser.visit("youtube.com")
    browser.visit("github.com")
    browser.visit("stackoverflow.com")
    print(f"Current: {browser.current_page()}")
    
    browser.back()
    browser.back()
    print(f"Current: {browser.current_page()}")
    
    browser.forward()
    print(f"Current: {browser.current_page()}")
    Python

    6. Undo/Redo Functionality

    class TextEditor:
        """Text editor with undo/redo functionality"""
    
        def __init__(self):
            self.text = ""
            self.undo_stack = []
            self.redo_stack = []
    
        def write(self, text):
            """Add text"""
            self.undo_stack.append(self.text)
            self.text += text
            self.redo_stack.clear()  # Clear redo history
            print(f"Text: '{self.text}'")
    
        def undo(self):
            """Undo last change"""
            if not self.undo_stack:
                print("Nothing to undo")
                return
    
            self.redo_stack.append(self.text)
            self.text = self.undo_stack.pop()
            print(f"After undo: '{self.text}'")
    
        def redo(self):
            """Redo last undone change"""
            if not self.redo_stack:
                print("Nothing to redo")
                return
    
            self.undo_stack.append(self.text)
            self.text = self.redo_stack.pop()
            print(f"After redo: '{self.text}'")
    
        def get_text(self):
            return self.text
    
    
    # Example usage
    editor = TextEditor()
    editor.write("Hello")
    editor.write(" World")
    editor.write("!")
    editor.undo()
    editor.undo()
    editor.redo()
    print(f"Final text: '{editor.get_text()}'")
    Python

    Common Problems

    1. Next Greater Element

    Find the next greater element for each element in an array.

    def next_greater_element(arr):
        """
        Find next greater element for each element
    
        Example: [4, 5, 2, 10, 8] → [5, 10, 10, -1, -1]
        """
        n = len(arr)
        result = [-1] * n
        stack = []
    
        # Traverse from right to left
        for i in range(n - 1, -1, -1):
            # Remove smaller elements
            while stack and stack[-1] <= arr[i]:
                stack.pop()
    
            # Top of stack is next greater element
            if stack:
                result[i] = stack[-1]
    
            # Push current element
            stack.append(arr[i])
    
        return result
    
    
    # Test
    arr = [4, 5, 2, 10, 8]
    result = next_greater_element(arr)
    print(f"Array:  {arr}")
    print(f"Result: {result}")
    Python

    2. Stock Span Problem

    Calculate the span of stock prices (number of consecutive days before current day with price less than or equal to current).

    def stock_span(prices):
        """
        Calculate stock span for each day
    
        Example: [100, 80, 60, 70, 60, 75, 85]
                 → [1, 1, 1, 2, 1, 4, 6]
        """
        n = len(prices)
        spans = [0] * n
        stack = []  # Store indices
    
        for i in range(n):
            # Pop elements while current price is greater
            while stack and prices[stack[-1]] <= prices[i]:
                stack.pop()
    
            # Calculate span
            if not stack:
                spans[i] = i + 1
            else:
                spans[i] = i - stack[-1]
    
            # Push current index
            stack.append(i)
    
        return spans
    
    
    # Test
    prices = [100, 80, 60, 70, 60, 75, 85]
    spans = stock_span(prices)
    print(f"Prices: {prices}")
    print(f"Spans:  {spans}")
    Python

    3. Largest Rectangle in Histogram

    Find the area of the largest rectangle in a histogram.

    def largest_rectangle_area(heights):
        """
        Find largest rectangle area in histogram
    
        Example: [2, 1, 5, 6, 2, 3] → 10
        """
        stack = []
        max_area = 0
        index = 0
    
        while index < len(heights):
            # If current bar is higher, push it
            if not stack or heights[index] >= heights[stack[-1]]:
                stack.append(index)
                index += 1
            else:
                # Calculate area with stack top as smallest bar
                top = stack.pop()
                width = index if not stack else index - stack[-1] - 1
                area = heights[top] * width
                max_area = max(max_area, area)
    
        # Pop remaining bars
        while stack:
            top = stack.pop()
            width = index if not stack else index - stack[-1] - 1
            area = heights[top] * width
            max_area = max(max_area, area)
    
        return max_area
    
    
    # Test
    heights = [2, 1, 5, 6, 2, 3]
    print(f"Heights: {heights}")
    print(f"Largest rectangle area: {largest_rectangle_area(heights)}")
    Python

    4. Valid Parentheses (LeetCode #20)

    def is_valid_parentheses(s):
        """
        Determine if string has valid parentheses
    
        Example: "()" → True, "([)]" → False
        """
        stack = []
        mapping = {')': '(', '}': '{', ']': '['}
    
        for char in s:
            if char in mapping:
                top_element = stack.pop() if stack else '#'
                if mapping[char] != top_element:
                    return False
            else:
                stack.append(char)
    
        return not stack
    
    
    # Test cases
    test_cases = ["()", "()[]{}", "(]", "([)]", "{[]}"]
    for test in test_cases:
        print(f"{test:10}{is_valid_parentheses(test)}")
    Python

    5. Min Stack (LeetCode #155)

    Design a stack that supports push, pop, top, and retrieving minimum element in constant time.

    class MinStack:
        """Stack with O(1) min operation"""
    
        def __init__(self):
            self.stack = []
            self.min_stack = []
    
        def push(self, val):
            self.stack.append(val)
            if not self.min_stack or val <= self.min_stack[-1]:
                self.min_stack.append(val)
    
        def pop(self):
            if self.stack:
                val = self.stack.pop()
                if val == self.min_stack[-1]:
                    self.min_stack.pop()
                return val
    
        def top(self):
            return self.stack[-1] if self.stack else None
    
        def get_min(self):
            return self.min_stack[-1] if self.min_stack else None
    
    
    # Example usage
    min_stack = MinStack()
    min_stack.push(-2)
    min_stack.push(0)
    min_stack.push(-3)
    print(f"Min: {min_stack.get_min()}")  # -3
    min_stack.pop()
    print(f"Top: {min_stack.top()}")      # 0
    print(f"Min: {min_stack.get_min()}")  # -2
    Python

    6. Decode String (LeetCode #394)

    def decode_string(s):
        """
        Decode encoded string
    
        Example: "3[a]2[bc]" → "aaabcbc"
                 "3[a2[c]]" → "accaccacc"
        """
        stack = []
        current_string = ""
        current_num = 0
    
        for char in s:
            if char.isdigit():
                current_num = current_num * 10 + int(char)
            elif char == '[':
                stack.append((current_string, current_num))
                current_string = ""
                current_num = 0
            elif char == ']':
                prev_string, num = stack.pop()
                current_string = prev_string + current_string * num
            else:
                current_string += char
    
        return current_string
    
    
    # Test cases
    test_cases = [
        "3[a]2[bc]",
        "3[a2[c]]",
        "2[abc]3[cd]ef"
    ]
    
    for test in test_cases:
        print(f"{test:20}{decode_string(test)}")
    Python

    Advanced Topics

    1. Two Stacks in One Array

    Implement two stacks using a single array efficiently.

    class TwoStacks:
        """Two stacks in one array"""
    
        def __init__(self, size):
            self.size = size
            self.arr = [None] * size
            self.top1 = -1
            self.top2 = size
    
        def push1(self, data):
            """Push to stack 1"""
            if self.top1 < self.top2 - 1:
                self.top1 += 1
                self.arr[self.top1] = data
            else:
                raise OverflowError("Stack Overflow")
    
        def push2(self, data):
            """Push to stack 2"""
            if self.top1 < self.top2 - 1:
                self.top2 -= 1
                self.arr[self.top2] = data
            else:
                raise OverflowError("Stack Overflow")
    
        def pop1(self):
            """Pop from stack 1"""
            if self.top1 >= 0:
                data = self.arr[self.top1]
                self.top1 -= 1
                return data
            raise IndexError("Stack 1 Underflow")
    
        def pop2(self):
            """Pop from stack 2"""
            if self.top2 < self.size:
                data = self.arr[self.top2]
                self.top2 += 1
                return data
            raise IndexError("Stack 2 Underflow")
    
    
    # Example usage
    ts = TwoStacks(10)
    ts.push1(1)
    ts.push1(2)
    ts.push2(10)
    ts.push2(20)
    print(f"Pop from stack 1: {ts.pop1()}")  # 2
    print(f"Pop from stack 2: {ts.pop2()}")  # 20
    Python

    2. Sort a Stack

    Sort a stack using only stack operations.

    def sort_stack(stack):
        """Sort stack in ascending order (smallest on top)"""
        temp_stack = []
    
        while stack:
            temp = stack.pop()
    
            while temp_stack and temp_stack[-1] > temp:
                stack.append(temp_stack.pop())
    
            temp_stack.append(temp)
    
        # Transfer back to original stack
        while temp_stack:
            stack.append(temp_stack.pop())
    
        return stack
    
    
    # Test
    stack = [34, 3, 31, 98, 92, 23]
    print(f"Original: {stack}")
    sorted_stack = sort_stack(stack)
    print(f"Sorted:   {sorted_stack}")
    Python

    3. Stack with Middle Element Operations

    class StackWithMiddle:
        """Stack that supports finding/deleting middle element in O(1)"""
    
        class Node:
            def __init__(self, data):
                self.data = data
                self.prev = None
                self.next = None
    
        def __init__(self):
            self.head = None
            self.mid = None
            self.count = 0
    
        def push(self, data):
            new_node = self.Node(data)
            new_node.next = self.head
            self.count += 1
    
            if self.count == 1:
                self.mid = new_node
            else:
                self.head.prev = new_node
                if self.count % 2 != 0:
                    self.mid = self.mid.prev
    
            self.head = new_node
    
        def pop(self):
            if self.count == 0:
                raise IndexError("Stack is empty")
    
            data = self.head.data
            self.head = self.head.next
    
            if self.head:
                self.head.prev = None
    
            self.count -= 1
    
            if self.count % 2 == 0 and self.mid:
                self.mid = self.mid.next
    
            return data
    
        def find_middle(self):
            if self.count == 0:
                raise IndexError("Stack is empty")
            return self.mid.data
    
        def delete_middle(self):
            if self.count == 0:
                raise IndexError("Stack is empty")
    
            data = self.mid.data
    
            if self.count == 1:
                self.head = None
                self.mid = None
            else:
                self.mid.prev.next = self.mid.next
                if self.mid.next:
                    self.mid.next.prev = self.mid.prev
    
                if self.count % 2 == 0:
                    self.mid = self.mid.prev
                else:
                    self.mid = self.mid.next
    
            self.count -= 1
            return data
    
    
    # Example usage
    stack = StackWithMiddle()
    for i in range(1, 6):
        stack.push(i)
        print(f"Pushed {i}, middle: {stack.find_middle()}")
    Python

    4. Celebrity Problem

    Find the celebrity in a party (person known by everyone but knows no one).

    def find_celebrity(matrix):
        """
        Find celebrity using stack
    
        matrix[i][j] = 1 means i knows j
        Celebrity: column is all 1s (except diagonal), row is all 0s
        """
        n = len(matrix)
        stack = list(range(n))
    
        # Find potential celebrity
        while len(stack) > 1:
            a = stack.pop()
            b = stack.pop()
    
            if matrix[a][b]:  # a knows b
                stack.append(b)  # a can't be celebrity
            else:  # a doesn't know b
                stack.append(a)  # b can't be celebrity
    
        # Verify the potential celebrity
        candidate = stack.pop()
    
        for i in range(n):
            if i != candidate:
                if matrix[candidate][i] or not matrix[i][candidate]:
                    return -1  # No celebrity
    
        return candidate
    
    
    # Test
    matrix = [
        [0, 1, 0],
        [0, 0, 0],
        [0, 1, 0]
    ]
    celebrity = find_celebrity(matrix)
    print(f"Celebrity: {celebrity}")  # Person 1
    Python

    5. Expression Evaluation with Operator Precedence

    Complete expression evaluator with proper operator precedence.

    def evaluate_expression(expression):
        """
        Evaluate mathematical expression with proper precedence
    
        Example: "3 + 5 * 2" → 13
        """
        def apply_operation(operators, values):
            operator = operators.pop()
            right = values.pop()
            left = values.pop()
    
            if operator == '+':
                values.append(left + right)
            elif operator == '-':
                values.append(left - right)
            elif operator == '*':
                values.append(left * right)
            elif operator == '/':
                values.append(left / right)
    
        def precedence(op):
            if op in '+-':
                return 1
            if op in '*/':
                return 2
            return 0
    
        values = []
        operators = []
        i = 0
    
        while i < len(expression):
            if expression[i] == ' ':
                i += 1
                continue
    
            # Number
            if expression[i].isdigit():
                num = 0
                while i < len(expression) and expression[i].isdigit():
                    num = num * 10 + int(expression[i])
                    i += 1
                values.append(num)
                continue
    
            # Opening parenthesis
            if expression[i] == '(':
                operators.append(expression[i])
    
            # Closing parenthesis
            elif expression[i] == ')':
                while operators and operators[-1] != '(':
                    apply_operation(operators, values)
                operators.pop()  # Remove '('
    
            # Operator
            else:
                while (operators and operators[-1] != '(' and
                       precedence(operators[-1]) >= precedence(expression[i])):
                    apply_operation(operators, values)
                operators.append(expression[i])
    
            i += 1
    
        # Apply remaining operators
        while operators:
            apply_operation(operators, values)
    
        return values[-1]
    
    
    # Test cases
    expressions = [
        "3 + 5 * 2",
        "10 + 2 * 6",
        "100 * 2 + 12",
        "100 * ( 2 + 12 )",
        "100 * ( 2 + 12 ) / 14"
    ]
    
    for expr in expressions:
        result = evaluate_expression(expr)
        print(f"{expr:25} = {result}")
    Python

    Stack Interview Questions Checklist

    Easy Level

    • ✓ Implement stack using arrays
    • ✓ Implement stack using linked list
    • ✓ Valid parentheses
    • ✓ Reverse a string using stack
    • ✓ Implement two stacks in an array

    Medium Level

    • ✓ Min stack
    • ✓ Next greater element
    • ✓ Stock span problem
    • ✓ Infix to postfix conversion
    • ✓ Evaluate postfix expression
    • ✓ Sort a stack
    • ✓ Decode string

    Hard Level

    • ✓ Largest rectangle in histogram
    • ✓ Maximal rectangle
    • ✓ Celebrity problem
    • ✓ Expression evaluation
    • ✓ Stack with middle element operations

    Best Practices

    1. When to Use Stack

    • Function call management (recursion)
    • Expression evaluation and parsing
    • Backtracking algorithms (DFS, maze solving)
    • Undo/redo operations
    • Browser history navigation
    • Syntax parsing and validation

    2. Common Pitfalls

    # ❌ Don't forget to check if stack is empty before pop/peek
    stack.pop()  # May raise error if empty
    
    # ✓ Always check before popping
    if stack:
        stack.pop()
    
    # ✓ Or handle exceptions
    try:
        stack.pop()
    except IndexError:
        print("Stack is empty")
    Python

    3. Memory Optimization

    # Use deque instead of list for better performance
    from collections import deque
    
    # ❌ Less efficient for large stacks
    stack = []
    
    # ✓ More efficient
    stack = deque()
    Python

    4. Type Hints and Documentation

    from typing import List, Optional, Any
    
    class Stack:
        """
        A LIFO (Last-In-First-Out) stack implementation.
    
        Attributes:
            _items: List storing stack elements
        """
    
        def __init__(self) -> None:
            self._items: List[Any] = []
    
        def push(self, item: Any) -> None:
            """Add item to top of stack."""
            self._items.append(item)
    
        def pop(self) -> Any:
            """Remove and return top item."""
            if self.is_empty():
                raise IndexError("pop from empty stack")
            return self._items.pop()
    
        def peek(self) -> Optional[Any]:
            """Return top item without removing."""
            return self._items[-1] if self._items else None
    
        def is_empty(self) -> bool:
            """Check if stack is empty."""
            return len(self._items) == 0
    
        def size(self) -> int:
            """Return number of items in stack."""
            return len(self._items)
    Python

    Summary

    Key Takeaways

    1. LIFO Principle: Last element in is first element out
    2. O(1) Operations: Push, pop, and peek are constant time
    3. Multiple Implementations: List, deque, linked list, LifoQueue
    4. Wide Applications: Expression parsing, backtracking, history management
    5. Interview Favorite: Common in coding interviews and algorithm problems

    Quick Reference

    # Create a stack
    stack = []
    
    # Basic operations
    stack.append(item)      # Push
    item = stack.pop()      # Pop
    item = stack[-1]        # Peek
    is_empty = len(stack) == 0
    size = len(stack)
    
    # Using deque (recommended)
    from collections import deque
    stack = deque()
    stack.append(item)      # Push
    item = stack.pop()      # Pop
    Python

    Further Reading

    • Time and Space Complexity Analysis
    • Recursion and Call Stack
    • Stack-based Memory Management
    • Advanced Data Structures (Monotonic Stack)
    • Compiler Design and Expression Parsing

    Practice Resources:

    • LeetCode Stack Problems
    • HackerRank Stack Challenges
    • GeeksforGeeks Stack Articles
    • Project Euler Stack Problems

    Happy Coding! 🚀


    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *