Skip to content

Stacks

Python implementation โ€” 9 questions covering core concepts and interview patterns.

STACKS โ€” Core Concepts & Interview Questions


"""
STACKS โ€” Core Concepts & Interview Questions
=============================================

Core Concepts:
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ€ข LIFO (Last-In-First-Out) data structure
โ€ข Python list can be used as a stack: append() for push, pop() for pop
โ€ข Undo/Redo operations, expression evaluation, backtracking
โ€ข Monotonic Stack pattern โ€” maintaining sorted order in the stack
โ€ข Stack-based DFS for tree/graph traversals

Common Patterns:
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โ€ข Next Greater Element (monotonic decreasing stack)
โ€ข Expression evaluation (two-stack algorithm: numbers + operators)
โ€ข Parentheses validation
โ€ข Min stack (track minimum with each element)
โ€ข Histogram area (largest rectangle)
"""

from typing import List, Optional, Tuple


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 1: Valid Parentheses
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def is_valid_parentheses(s: str) -> bool:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given a string s containing '(', ')', '{', '}', '[', ']', determine
    if the input string is valid. Open brackets must be closed in the
    correct order.

    Example:
        Input: "()[]{}"
        Output: true
        Input: "([)]"
        Output: false

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Use stack to track opening brackets
    2. When we see a closing bracket, check if it matches the top of stack
    3. If stack is empty when seeing a closing bracket โ†’ invalid
    4. If stack is not empty at end โ†’ invalid (unclosed brackets)
    5. Key: Map closing to opening for O(1) matching check

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” Single pass through the string
    Space: O(n) โ€” Stack can hold up to n opening brackets
    """
    matching = {')': '(', '}': '{', ']': '['}
    stack = []

    for char in s:
        if char in matching:
            # Closing bracket โ€” must match top of stack
            if not stack or stack[-1] != matching[char]:
                return False
            stack.pop()
        else:
            # Opening bracket โ€” push to stack
            stack.append(char)

    return len(stack) == 0


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 2: Min Stack
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

class MinStack:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Design a stack that supports push, pop, top, and retrieving the
    minimum element in O(1) time.

    Example:
        MinStack minStack = new MinStack()
        minStack.push(-2)
        minStack.push(0)
        minStack.push(-3)
        minStack.getMin() โ†’ -3
        minStack.pop()
        minStack.top() โ†’ 0
        minStack.getMin() โ†’ -2

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Naive: Scan entire stack for min on getMin() โ€” O(n)
    2. Better: Store (value, current_min) pairs in stack
       - Push: new_min = min(value, current_min)
       - Pop: Just pop, min is restored automatically
       - This works because each element carries the min at that point
    3. Space-optimized: Use a separate min stack
       - Push to min stack when val <= current min (the <= matters:
         with duplicates of the minimum, using < would pop the min too
         early)
       - Pop from min stack when the popped value equals its top
       - Saves space when min doesn't change frequently
    4. O(1)-extra-space trick (store 2*val - min encoded values) exists,
       but in Java/Go it can overflow 32-bit ints; mention it, don't lead
       with it

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(1) โ€” All operations are O(1)
    Space: O(n) โ€” Stack stores n elements with their min values
    """

    def __init__(self):
        self._stack = []  # (value, min_at_this_point)

    def push(self, val: int) -> None:
        current_min = self.get_min()
        new_min = min(val, current_min) if current_min is not None else val
        self._stack.append((val, new_min))

    def pop(self) -> None:
        if self._stack:
            self._stack.pop()

    def top(self) -> Optional[int]:
        if self._stack:
            return self._stack[-1][0]
        return None

    def get_min(self) -> Optional[int]:
        if self._stack:
            return self._stack[-1][1]
        return None


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 3: Daily Temperatures (Next Greater Element)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def daily_temperatures(temperatures: List[int]) -> List[int]:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given an array of daily temperatures, return an array answer such that
    answer[i] is the number of days until a warmer temperature. If no
    warmer day exists, answer[i] = 0.

    Example:
        Input: [73, 74, 75, 71, 69, 72, 76, 73]
        Output: [1, 1, 4, 2, 1, 1, 0, 0]

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Brute Force: For each day, scan forward for warmer โ€” O(nยฒ)
    2. Monotonic Decreasing Stack:
       - Stack stores indices (not values) with decreasing temperatures
       - When we see a temp > stack top's temp โ†’ we've found the answer
         for the stack top index
       - Pop from stack and compute days difference
    3. Why monotonically decreasing? We want to find "next greater"
       - Stack contains indices waiting for a warmer day
       - Since we process left to right, stack naturally decreases

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” Each index pushed and popped at most once
    Space: O(n) โ€” Stack can hold up to n indices
    """
    n = len(temperatures)
    result = [0] * n
    stack = []  # indices with decreasing temperatures

    for i in range(n):
        # While current temp > temp at top of stack
        while stack and temperatures[i] > temperatures[stack[-1]]:
            idx = stack.pop()
            result[idx] = i - idx
        stack.append(i)

    return result


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 4: Largest Rectangle in Histogram
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def largest_rectangle_area(heights: List[int]) -> int:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given an array of bar heights in a histogram, find the largest
    rectangle that can be formed.

    Example:
        Input: [2, 1, 5, 6, 2, 3]
        Output: 10  (5 x 2 rectangle at indices 2-3)

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Brute Force: For each bar, expand left and right โ€” O(nยฒ)
    2. Monotonic Increasing Stack:
       - Process bars left to right, maintaining increasing heights in stack
       - When we see a shorter bar, we know the right boundary for the
         popped bar (current index). Left boundary is the new stack top
       - Area = height * (right_boundary - left_boundary - 1)
    3. Why increasing stack: When heights increase, rectangles can extend
       to the right. When a shorter bar appears, we "finalize" rectangles
       that can't extend further.
    4. Append 0 to handle remaining bars in stack at the end

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” Each bar pushed and popped at most once
    Space: O(n) โ€” Stack stores indices
    """
    stack = []  # indices with increasing heights
    max_area = 0
    heights = heights + [0]  # Sentinel to force processing remaining bars

    for i, h in enumerate(heights):
        # Pop while current height < height at stack top
        while stack and h < heights[stack[-1]]:
            height = heights[stack.pop()]
            # Left boundary is the new stack top (or -1 if empty)
            left = stack[-1] if stack else -1
            width = i - left - 1
            max_area = max(max_area, height * width)
        stack.append(i)

    return max_area


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 5: Evaluate Reverse Polish Notation
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def eval_rpn(tokens: List[str]) -> int:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Evaluate the value of an arithmetic expression in Reverse Polish
    Notation. Valid operators: +, -, *, /. Division truncates toward zero.

    Example:
        Input: ["2", "1", "+", "3", "*"]
        Output: 9  ((2 + 1) * 3)

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Stack-based evaluation:
       - Push numbers onto stack
       - When encountering operator, pop two operands, compute, push result
    2. Order matters: For subtraction and division, second pop is left operand
       e.g., "5 3 -" โ†’ pop 3, then pop 5 โ†’ 5 - 3 = 2
    3. Integer division must truncate toward zero (Java/Go/C semantics).
       Python's // floors toward negative infinity: -7 // 2 == -4, but
       the answer here is -3. int(a / b) is the common shortcut, but it
       goes through a float and loses precision past 2โตยณ, so we divide
       magnitudes with // and reapply the sign.

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” Single pass through tokens
    Space: O(n) โ€” Stack can hold up to (n+1)/2 operands
    """
    stack = []

    for token in tokens:
        if token in ('+', '-', '*', '/'):
            b = stack.pop()
            a = stack.pop()

            if token == '+':
                stack.append(a + b)
            elif token == '-':
                stack.append(a - b)
            elif token == '*':
                stack.append(a * b)
            else:  # division: truncate toward zero, exactly
                q = abs(a) // abs(b)
                stack.append(q if (a >= 0) == (b > 0) else -q)
        else:
            stack.append(int(token))

    return stack[0]


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 6: Next Greater Element (Circular)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def next_greater_elements(nums: List[int]) -> List[int]:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given a circular array, find the Next Greater Element for every element.
    The next greater element is the first greater number in a traversal
    order (including wrapping around).

    Example:
        Input: [1, 2, 1]
        Output: [2, -1, 2]

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Circular array trick: Process the array twice (simulate by
       iterating 2*n with modulo index)
    2. Use monotonic decreasing stack (like daily temperatures)
    3. Only push results for first n elements (not duplicates)
    4. Stack stores indices of elements waiting for next greater

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” 2n iterations; each index pushed and popped at most once
    Space: O(n) โ€” Result array + stack

    EDGE CASES:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    โ€ข The maximum (and duplicates of it) stay -1 โ€” nothing is strictly
      greater even after wrapping
    โ€ข All equal โ€” every answer is -1 (strict >)
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # indices waiting for next greater element

    # Iterate twice to handle circularity
    for i in range(2 * n):
        idx = i % n
        while stack and nums[idx] > nums[stack[-1]]:
            popped = stack.pop()
            if result[popped] == -1:  # Only set once
                result[popped] = nums[idx]
        # Only push indices from first pass
        if i < n:
            stack.append(idx)

    return result


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 7: Validate Stack Sequences
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def validate_stack_sequences(pushed: List[int], popped: List[int]) -> bool:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given two sequences pushed and popped, return true if this could
    be the result of stack push/pop operations.

    Example:
        Input: pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
        Output: true
        Input: pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
        Output: false

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Simulate the process: push elements until top matches next pop
    2. Greedy: Always pop when the top of stack matches the next element
       in popped sequence
    3. If all elements are pushed and stack is empty โ†’ valid sequence
    4. This works because at any point, if we can pop, we should
       (delaying pop can never help create a valid sequence)

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” Each element pushed and popped at most once
    Space: O(n) โ€” Stack storage

    ASSUMPTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Values are distinct and popped is a permutation of pushed (the
    LeetCode constraint). With duplicates, greedy popping is ambiguous.
    """
    stack = []
    pop_idx = 0

    for num in pushed:
        stack.append(num)

        # Greedily pop while top matches next popped element
        while stack and stack[-1] == popped[pop_idx]:
            stack.pop()
            pop_idx += 1

    return len(stack) == 0


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 8: Decode String
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def decode_string(s: str) -> str:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Given an encoded string, return its decoded string.
    Encoding: k[encoded_string] means the encoded_string is repeated k times.

    Example:
        Input: "3[a]2[bc]"
        Output: "aaabcbc"
        Input: "3[a2[c]]"
        Output: "accaccacc"

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. Use stack to handle nested encodings
    2. Two stacks: one for counts, one for partial strings
    3. When we see '[', push current count and current string to stacks
    4. When we see ']', pop and build โ†’ repeat current string and append
       to previous string
    5. This naturally handles nesting because inner brackets are processed
       first (LIFO property of stack)

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n + Lยทd) โ€” n = input length, L = decoded length, d = nesting
          depth. NOT O(n): "3[3[3[a]]]" is 10 chars but decodes to 27,
          and each nested level re-copies the string it builds.
    Space: O(L) โ€” The decoded string and the partial strings on the stack

    EDGE CASES:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    โ€ข Multi-digit counts "12[a]" โ€” accumulate current_count * 10 + d
    โ€ข Letters after a bracket "2[a]bc" โ€” appended to the outer string
    """
    count_stack = []
    string_stack = []
    current_string = ""
    current_count = 0

    for char in s:
        if char.isdigit():
            current_count = current_count * 10 + int(char)
        elif char == '[':
            count_stack.append(current_count)
            string_stack.append(current_string)
            current_string = ""
            current_count = 0
        elif char == ']':
            count = count_stack.pop()
            prev_string = string_stack.pop()
            current_string = prev_string + current_string * count
        else:
            current_string += char

    return current_string


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUESTION 9: Basic Calculator II (+ - * / with precedence)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def calculate(s: str) -> int:
    """
    QUESTION:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Evaluate a string expression containing non-negative integers, the
    operators + - * /, and spaces (no parentheses). Integer division
    truncates toward zero. The expression is always valid.

    Example:
        Input: " 3+5 / 2 "
        Output: 5

    THOUGHT PROCESS:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    1. The difficulty is precedence: * and / bind tighter than + and -.
    2. Stack of signed terms: when an operator ends a number, act on the
       PREVIOUS operator:
       - '+' โ†’ push num;  '-' โ†’ push -num
       - '*' / '/' โ†’ pop the last term, combine it with num, push back
       The answer is sum(stack): every remaining entry is an additive term.
    3. Trigger evaluation when we hit an operator OR the last character,
       otherwise the final number is never applied (the classic bug).
    4. O(1)-space variant: keep only the last term in a variable instead
       of a stack. With parentheses (Basic Calculator I/III), recurse or
       push the running result and sign on '('.

    COMPLEXITY:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    Time: O(n) โ€” One pass
    Space: O(n) โ€” The stack of terms (O(1) with the last-term variant)

    EDGE CASES:
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    โ€ข "14-3/2" โ€” the 3 is pushed as -3, so the division sees -3 / 2.
      Truncating gives -1 (answer 13); Python's floor // would give -2
      (answer 12). Truncation is what makes trunc(-b/c) == -(b/c) hold.
    โ€ข Multi-digit numbers and arbitrary spaces
    """
    stack: List[int] = []
    num = 0
    prev_op = '+'

    for i, ch in enumerate(s):
        if ch.isdigit():
            num = num * 10 + int(ch)
        if (ch in '+-*/') or i == len(s) - 1:
            if prev_op == '+':
                stack.append(num)
            elif prev_op == '-':
                stack.append(-num)
            elif prev_op == '*':
                stack.append(stack.pop() * num)
            else:  # '/': truncate toward zero
                top = stack.pop()
                q = abs(top) // num
                stack.append(q if top >= 0 else -q)
            prev_op = ch
            num = 0

    return sum(stack)


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# DEMO
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def demo():
    print("=" * 70)
    print("STACKS โ€” Interview Questions Demo")
    print("=" * 70)

    # Q1
    print("\n1๏ธโƒฃ  Valid Parentheses")
    print("-" * 40)
    tests = ["()[]{}", "([)]", "({[]})"]
    for t in tests:
        print(f"   \"{t}\": {is_valid_parentheses(t)}")

    # Q2
    print("\n2๏ธโƒฃ  Min Stack")
    print("-" * 40)
    ms = MinStack()
    ms.push(-2)
    ms.push(0)
    ms.push(-3)
    print(f"   After pushes [-2, 0, -3]:")
    print(f"   getMin(): {ms.get_min()}")
    ms.pop()
    print(f"   After pop:")
    print(f"   top(): {ms.top()}")
    print(f"   getMin(): {ms.get_min()}")

    # Q3
    print("\n3๏ธโƒฃ  Daily Temperatures")
    print("-" * 40)
    temps = [73, 74, 75, 71, 69, 72, 76, 73]
    print(f"   Input: {temps}")
    print(f"   Output: {daily_temperatures(temps)}")

    # Q4
    print("\n4๏ธโƒฃ  Largest Rectangle in Histogram")
    print("-" * 40)
    heights = [2, 1, 5, 6, 2, 3]
    print(f"   Input: {heights}")
    print(f"   Largest Area: {largest_rectangle_area(heights)}")

    # Q5
    print("\n5๏ธโƒฃ  Evaluate Reverse Polish Notation")
    print("-" * 40)
    tokens = ["2", "1", "+", "3", "*"]
    print(f"   Input: {tokens}")
    print(f"   Result: {eval_rpn(tokens)}")

    # Q6
    print("\n6๏ธโƒฃ  Next Greater Element (Circular)")
    print("-" * 40)
    nums = [1, 2, 1]
    print(f"   Input: {nums}")
    print(f"   Output: {next_greater_elements(nums)}")

    # Q7
    print("\n7๏ธโƒฃ  Validate Stack Sequences")
    print("-" * 40)
    pushed = [1, 2, 3, 4, 5]
    popped1 = [4, 5, 3, 2, 1]
    popped2 = [4, 3, 5, 1, 2]
    print(f"   pushed={pushed}")
    print(f"   popped={popped1}: {validate_stack_sequences(pushed, popped1)}")
    print(f"   popped={popped2}: {validate_stack_sequences(pushed, popped2)}")

    # Q8
    print("\n8๏ธโƒฃ  Decode String")
    print("-" * 40)
    tests = ["3[a]2[bc]", "3[a2[c]]"]
    for t in tests:
        print(f"   \"{t}\" โ†’ \"{decode_string(t)}\"")

    # Q9
    print("\n9๏ธโƒฃ  Basic Calculator II")
    print("-" * 40)
    for expr in ["3+2*2", " 3/2 ", " 3+5 / 2 ", "14-3/2"]:
        print(f"   \"{expr}\" โ†’ {calculate(expr)}")

    print("\n" + "=" * 70)


if __name__ == "__main__":
    demo()

โ† Back to DSA Overview