Published on

leetcode-155 Min Stack

Authors
  • avatar
    Name
    Gene Zhang
    Twitter

[155] Min Stack

Key Concept: Auxiliary Stack for Tracking Minimum - Maintain two stacks: one for actual values, one for tracking minimum at each level. When pushing, also push current minimum onto min stack. This keeps getMin() at O(1).

Alternative: Store (value, current_min) pairs in a single stack.

# Design a stack that supports push, pop, top, and retrieving the minimum
# element in constant time.
# Implement the MinStack class with:
# - MinStack() initializes the stack object.
# - void push(int val) pushes val onto the stack.
# - void pop() removes the element on top of the stack.
# - int top() gets the top element of the stack.
# - int getMin() retrieves the minimum element in the stack.
# You must implement a solution with O(1) time complexity for each function.
#
# Example:
# Input: ["MinStack","push","push","push","getMin","pop","top","getMin"]
#        [[],[-2],[0],[-3],[],[],[],[]]
# Output: [null,null,null,null,-3,null,0,-2]
#
# Constraints:
# -2^31 <= val <= 2^31 - 1
# pop, top and getMin will always be called on non-empty stacks.

class MinStack:
    # Approach 1: Two stacks
    def __init__(self):
        self.stack = []  # Main stack
        self.min_stack = []  # Stack tracking minimums

    def push(self, val: int) -> None:
        self.stack.append(val)
        # Push current minimum onto min_stack
        if not self.min_stack:
            self.min_stack.append(val)
        else:
            self.min_stack.append(min(val, self.min_stack[-1]))

    def pop(self) -> None:
        self.stack.pop()
        self.min_stack.pop()

    def top(self) -> int:
        return self.stack[-1]

    def getMin(self) -> int:
        return self.min_stack[-1]


# Approach 2: Single stack with pairs
class MinStack2:
    def __init__(self):
        # Stack stores (value, current_min) pairs
        self.stack = []

    def push(self, val: int) -> None:
        if not self.stack:
            self.stack.append((val, val))
        else:
            current_min = min(val, self.stack[-1][1])
            self.stack.append((val, current_min))

    def pop(self) -> None:
        self.stack.pop()

    def top(self) -> int:
        return self.stack[-1][0]

    def getMin(self) -> int:
        return self.stack[-1][1]


# Approach 3: Space optimized (only store min when it changes)
class MinStack3:
    def __init__(self):
        self.stack = []
        self.min_stack = []

    def push(self, val: int) -> None:
        self.stack.append(val)
        # Only push to min_stack if it's a new minimum
        if not self.min_stack or val <= self.min_stack[-1]:
            self.min_stack.append(val)

    def pop(self) -> None:
        val = self.stack.pop()
        # Pop from min_stack if we're removing the current minimum
        if val == self.min_stack[-1]:
            self.min_stack.pop()

    def top(self) -> int:
        return self.stack[-1]

    def getMin(self) -> int:
        return self.min_stack[-1]

# All operations: Time O(1), Space O(n)
#
# Key Pattern: Auxiliary data structure to track additional information
# Trade space for time to achieve O(1) operations
#
# Similar problems: Max Stack, Stack with Middle Element