Stack with Constant-Time Minimum over a Sequence of Operations
Company: Snowflake
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Design a stack that supports pushing an element, popping the top element, reading the top element, and reading the smallest element currently in the stack, with every one of these operations taking constant time. To make the design testable as a single function, process a sequence of operations on an initially empty stack and return the values that the reading operations produce.
### Function Signature
```python
def run_min_stack(ops: list[str], args: list[int]) -> list[int]:
```
### Rules
- `ops[i]` is one of `"push"`, `"pop"`, `"top"`, `"get_min"`.
- `"push"` pushes `args[i]`. For every other operation, `args[i]` is `0` and is ignored.
- `"pop"` removes the top element and produces no output.
- `"top"` outputs the top element; `"get_min"` outputs the smallest element currently in the stack. Neither removes anything.
- The stack can hold equal values; each push is a separate element.
- Return the outputs of `"top"` and `"get_min"` in the order the operations occur. If there are none, return an empty list.
- Each operation must run in O(1) time; the tests include long sequences on which scanning the stack for every `"get_min"` is too slow.
### Constraints
- `1 <= len(ops) = len(args) <= 10^5`
- for `"push"`, `-2^31 <= args[i] <= 2^31 - 1`, the full signed 32-bit range
- `"pop"`, `"top"` and `"get_min"` are only issued when the stack is non-empty
### Examples
**Example 1**
```text
Input: ops = ["push", "push", "push", "get_min", "pop", "top", "get_min"]
args = [-2, 0, -3, 0, 0, 0, 0]
Output: [-3, 0, -2]
```
After the three pushes the stack is `[-2, 0, -3]` (top last) and its minimum is `-3`. Popping removes `-3`; the top is then `0` and the minimum is `-2`.
**Example 2**
```text
Input: ops = ["push", "push", "push", "get_min", "pop", "get_min", "pop", "get_min"]
args = [2, 1, 1, 0, 0, 0, 0, 0]
Output: [1, 1, 2]
```
Two copies of the minimum `1` are pushed. After one is popped the minimum is still `1`; after the second is popped it becomes `2`.
Overview: Design a stack that supports push, pop, top and reading its current minimum in constant time, then run it over a sequence of operations and return what the reads produce. Tests data structure design, duplicate minimum values and keeping auxiliary state consistent on every pop.
Design a stack that supports pushing an element, popping the top element, reading the top element, and reading the smallest element currently in the stack, with every one of these operations taking constant time.
To make the design testable as a single function, implement `run_min_stack(ops, args)`: apply the operations in `ops` (with their arguments in `args`), in order, to an initially empty stack, and return the values that the reading operations produce.
### Rules
- `ops[i]` is one of `"push"`, `"pop"`, `"top"`, `"get_min"`.
- `"push"` pushes `args[i]`. For every other operation, `args[i]` is `0` and is ignored.
- `"pop"` removes the top element and produces no output.
- `"top"` outputs the top element; `"get_min"` outputs the smallest element currently in the stack. Neither removes anything.
- The stack can hold equal values; each push is a separate element.
- Return the outputs of `"top"` and `"get_min"` in the order the operations occur. If there are none, return an empty list.
- Each operation must run in O(1) time; scanning the stack for every `"get_min"` is too slow on long operation sequences.
### Constraints
- `1 <= len(ops) = len(args) <= 10^5`
- For `"push"`, `-2^31 <= args[i] <= 2^31 - 1`, the full signed 32-bit range; for every other operation `args[i]` is `0`.
- `"pop"`, `"top"` and `"get_min"` are only issued when the stack is non-empty.
- Every returned value is one of the pushed values, so no value exceeds 2^31 - 1 and every value fits in a signed 32-bit integer.
### Example 1
```text
Input: ops = ["push", "push", "push", "get_min", "pop", "top", "get_min"]
args = [-2, 0, -3, 0, 0, 0, 0]
Output: [-3, 0, -2]
```
After the three pushes the stack is `[-2, 0, -3]` (top last) and its minimum is `-3`. Popping removes `-3`; the top is then `0` and the minimum is `-2`.
### Example 2
```text
Input: ops = ["push", "push", "push", "get_min", "pop", "get_min", "pop", "get_min"]
args = [2, 1, 1, 0, 0, 0, 0, 0]
Output: [1, 1, 2]
```
Two copies of the minimum `1` are pushed. After one is popped the minimum is still `1`; after the second is popped it becomes `2`.
Constraints
- 1 <= len(ops) = len(args) <= 10^5
- ops[i] is one of "push", "pop", "top", "get_min"
- For "push", -2^31 <= args[i] <= 2^31 - 1, the full signed 32-bit range; for every other operation args[i] is 0 and is ignored
- "pop", "top" and "get_min" are only issued when the stack is non-empty
- Every returned value is a pushed value, so no value exceeds 2^31 - 1 and every value fits in a signed 32-bit integer
Examples
Input: (['push'], [5])
Expected Output: []
Explanation: Minimum valid: a single push and no reading operation, so the result is empty.
Input: (['push', 'top'], [7, 0])
Expected Output: [7]
Explanation: Minimum valid: a single push followed by one top.
Hints
- After the smallest element is popped, the next "get_min" must be answered immediately, without looking through the rest of the stack.
- Equal values are separate elements: popping one copy of the minimum must not forget the copies still on the stack.
- Only "top" and "get_min" add to the result, in operation order; a sequence without them returns an empty list.