Views

Report Content

×

Stacks in DSA: LIFO Principles & Applications

Master stack operations, implementation variants, and real-world applications. This tutorial covers parentheses matching, expression evaluation, and common interview problems with coding examples.

Stack Usage in Systems (2023)

Function Calls (40%)
Parsers (25%)
Undo Operations (20%)
Other (15%)

1. Stack Fundamentals

Stack (LIFO Data Structure)

Definition

Stack is a linear data structure that follows the Last In First Out (LIFO) principle. Elements are added (pushed) and removed (popped) from the same end called the top. Stacks are used extensively in function call management, expression evaluation, and backtracking algorithms.

How It Works

1
Push Operation
  • Add element to the top of stack
  • Update top pointer/reference
  • O(1) time complexity
2
Pop Operation
  • Remove and return top element
  • Check for underflow (empty stack)
  • O(1) time complexity
3
Peek/Top Operation
  • Return top element without removing
  • Useful for inspection
  • O(1) time complexity

Array-based Stack Implementation

class ArrayStack:
    def __init__(self):
        self.items = []
    
    def push(self, item):
        self.items.append(item)
    
    def pop(self):
        if self.is_empty():
            raise IndexError("Stack underflow")
        return self.items.pop()
    
    def peek(self):
        if self.is_empty():
            return None
        return self.items[-1]
    
    def is_empty(self):
        return len(self.items) == 0
    
    def size(self):
        return len(self.items)

Linked List-based Stack Implementation

class ListNode:
    def __init__(self, val, next_node=None):
        self.val = val
        self.next = next_node

class LinkedListStack:
    def __init__(self):
        self.top = None
    
    def push(self, val):
        self.top = ListNode(val, self.top)
    
    def pop(self):
        if self.is_empty():
            raise IndexError("Stack underflow")
        val = self.top.val
        self.top = self.top.next
        return val
    
    def peek(self):
        if self.is_empty():
            return None
        return self.top.val
    
    def is_empty(self):
        return self.top is None

Complexity: Push O(1), Pop O(1), Peek O(1)
Space: O(n) for n elements
Use When: LIFO access pattern needed

2. Valid Parentheses

Parentheses Matching

Definition

Valid Parentheses checks if a string of brackets is properly matched and nested. Each opening bracket must have a corresponding closing bracket of the same type in the correct order.

How It Works

1
Initialize stack
  • Create empty stack
  • Define matching pairs
2
Process each character
  • If opening bracket, push to stack
  • If closing bracket, check if matches top
  • If mismatch or stack empty → invalid
3
Verify complete matching
  • After processing all characters
  • Stack must be empty for valid string
def is_valid_parentheses(s: str) -> bool:
    stack = []
    pairs = {
        '(': ')',
        '[': ']',
        '{': '}'
    }
    
    for char in s:
        if char in pairs:  # Opening bracket
            stack.append(char)
        else:  # Closing bracket
            if not stack or pairs[stack.pop()] != char:
                return False
    
    return len(stack) == 0

# Test cases
print(is_valid_parentheses("()"))      # True
print(is_valid_parentheses("()[]{}"))  # True
print(is_valid_parentheses("(]"))      # False
print(is_valid_parentheses("([)]"))    # False
print(is_valid_parentheses("{[]}"))    # True

Complexity: O(n) time, O(n) space
Use When: Validating expressions, code parsing

3. Postfix Expression Evaluation

Postfix (Reverse Polish Notation) Evaluation

Definition

Postfix Evaluation computes the result of an expression where operators follow their operands (e.g., "3 4 +" = 7). Stacks naturally evaluate RPN without needing parentheses.

How It Works

1
Initialize stack
  • Create empty stack for operands
  • Split expression into tokens
2
Process each token
  • If number, push to stack
  • If operator, pop two operands
  • Apply operation, push result
3
Return final result
  • Stack contains single value
  • That value is the expression result
def evaluate_postfix(expression):
    stack = []
    operators = {
        '+': lambda a, b: a + b,
        '-': lambda a, b: a - b,
        '*': lambda a, b: a * b,
        '/': lambda a, b: int(a / b),  # Integer division
        '^': lambda a, b: a ** b
    }
    
    for token in expression.split():
        if token in operators:
            b = stack.pop()
            a = stack.pop()
            result = operators[token](a, b)
            stack.append(result)
        else:
            stack.append(int(token))
    
    return stack.pop()

# Example
expr = "3 4 + 2 * 7 /"  # (3+4)*2/7 = 2
print(evaluate_postfix(expr))  # 2

expr2 = "5 1 2 + 4 * + 3 -"  # 5+((1+2)*4)-3 = 14
print(evaluate_postfix(expr2))  # 14

Complexity: O(n) time, O(n) space
Use When: Calculator implementation, expression evaluation

4. Min Stack (Constant Time Minimum)

Min Stack with O(1) Get Minimum

Definition

Min Stack is a stack that supports push, pop, top, and getMin operations all in O(1) time. It uses an auxiliary stack to track minimum values at each state.

How It Works

1
Maintain two stacks
  • Main stack stores all elements
  • Min stack stores current minimums
2
Push operation
  • Push value to main stack
  • If min stack empty or value ≤ current min, push to min stack
3
Pop operation
  • Pop from main stack
  • If popped value equals min stack top, pop from min stack
class MinStack:
    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 not self.stack:
            return None
        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(5)
min_stack.push(2)
min_stack.push(7)
min_stack.push(1)
print(min_stack.get_min())  # 1
min_stack.pop()  # removes 1
print(min_stack.get_min())  # 2

Complexity: All operations O(1) time, O(n) space
Use When: Need constant time minimum retrieval

5. Monotonic Stack (Next Greater Element)

Next Greater Element Using Monotonic Stack

Definition

Monotonic Stack maintains elements in increasing or decreasing order. It's useful for finding next greater/smaller elements efficiently in O(n) time.

How It Works

1
Initialize empty stack
  • Stack stores indices of elements
  • Results array initialized with -1
2
Process each element
  • While stack not empty and current > element at stack top
  • Pop index and record current as next greater
3
Push current index to stack
  • Remaining indices have no greater element
  • Result holds -1 for those
def next_greater_elements(nums):
    result = [-1] * len(nums)
    stack = []
    
    for i in range(len(nums)):
        while stack and nums[i] > nums[stack[-1]]:
            idx = stack.pop()
            result[idx] = nums[i]
        stack.append(i)
    
    return result

def next_smaller_elements(nums):
    result = [-1] * len(nums)
    stack = []
    
    for i in range(len(nums)):
        while stack and nums[i] < nums[stack[-1]]:
            idx = stack.pop()
            result[idx] = nums[i]
        stack.append(i)
    
    return result

# Example
nums = [4, 2, 9, 7, 5, 3, 6]
print(next_greater_elements(nums))  # [9, 9, -1, -1, 6, 6, -1]
print(next_smaller_elements(nums))  # [2, -1, 7, 5, 3, -1, -1]

Complexity: O(n) time, O(n) space
Use When: Next greater/smaller element problems

6. Queue Using Two Stacks

Implement Queue with Stacks

Definition

Queue Using Stacks implements First In First Out (FIFO) behavior using two LIFO stacks. One stack handles enqueue operations, the other handles dequeue operations.

How It Works

1
Maintain two stacks
  • inputStack: handles push/enqueue
  • outputStack: handles pop/dequeue
2
Enqueue operation
  • Simply push to inputStack
  • O(1) time
3
Dequeue operation
  • If outputStack empty, transfer all from inputStack
  • Pop from outputStack
  • Amortized O(1) time
class QueueUsingStacks:
    def __init__(self):
        self.input_stack = []
        self.output_stack = []
    
    def enqueue(self, val):
        self.input_stack.append(val)
    
    def dequeue(self):
        if not self.output_stack:
            while self.input_stack:
                self.output_stack.append(self.input_stack.pop())
        
        if not self.output_stack:
            raise IndexError("Queue is empty")
        
        return self.output_stack.pop()
    
    def peek(self):
        if not self.output_stack:
            while self.input_stack:
                self.output_stack.append(self.input_stack.pop())
        
        if not self.output_stack:
            return None
        
        return self.output_stack[-1]
    
    def is_empty(self):
        return len(self.input_stack) == 0 and len(self.output_stack) == 0

# Example usage
q = QueueUsingStacks()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.dequeue())  # 1
print(q.dequeue())  # 2
q.enqueue(4)
print(q.dequeue())  # 3
print(q.dequeue())  # 4

Complexity: Enqueue O(1), Dequeue amortized O(1)
Use When: Need queue behavior with limited stack implementations

Stack Problem-Solving Cheat Sheet

Problem Type Approach Time Complexity
Parentheses Validation Push opens, pop on closes O(n)
Next Greater Element Monotonic decreasing stack O(n)
Largest Rectangle in Histogram Monotonic stack with indices O(n)
Infix to Postfix Conversion Operator precedence stack O(n)
Validating Stack Sequences Simulate with single stack O(n)
Min Stack (constant time minimum) Auxiliary min stack O(1) per op

Stack Implementation Checklist

Handle empty stack cases (underflow) properly
Consider using array for fixed capacity, linked list for dynamic
Use auxiliary stacks for min/max tracking
Monotonic stack pattern for next greater/smaller problems
Two-stack technique for implementing queues
Be mindful of recursion stack limits for deep recursion
Use peek() when inspection without removal is needed

7. Real-World Applications

Browser History (Back/Forward)

Two-stack approach: Manage navigation history

class BrowserHistory:
    def __init__(self, homepage):
        self.back_stack = [homepage]
        self.forward_stack = []
    
    def visit(self, url):
        self.back_stack.append(url)
        self.forward_stack.clear()
    
    def back(self, steps):
        while steps > 0 and len(self.back_stack) > 1:
            self.forward_stack.append(self.back_stack.pop())
            steps -= 1
        return self.back_stack[-1]
    
    def forward(self, steps):
        while steps > 0 and self.forward_stack:
            self.back_stack.append(self.forward_stack.pop())
            steps -= 1
        return self.back_stack[-1]

Text Editor Undo/Redo

Two-stack pattern: Track edit history

class TextEditor:
    def __init__(self):
        self.undo_stack = []
        self.redo_stack = []
    
    def type(self, text):
        self.undo_stack.append(('type', text))
        self.redo_stack.clear()
    
    def delete(self, count):
        self.undo_stack.append(('delete', count))
        self.redo_stack.clear()
    
    def undo(self):
        if self.undo_stack:
            action = self.undo_stack.pop()
            self.redo_stack.append(action)
    
    def redo(self):
        if self.redo_stack:
            action = self.redo_stack.pop()
            self.undo_stack.append(action)

DSA Expert Insight: Stack problems appear in 20% of technical interviews. Mastering LIFO principles and pattern recognition can help solve complex problems with elegant solutions. The monotonic stack pattern is particularly powerful for solving next greater/smaller element problems in O(n) time. Always consider edge cases like empty stacks and handle underflow gracefully.

Share and Join the Discussion

You need to be logged in to participate in this discussion.

×
×