Start learning free

LESSON 401 OF 543 · STACK

Min Stack in Rust: O(1) get_min with a Second Vec

Practice O(1) Read the full explanation

A stack hands its values back in the opposite order they arrived: the last value pushed is the first one popped. Design a stack that can also say, at any moment, which value is the smallest of all the values it holds, and do every operation in constant time.

Implement the class MinStack:

  • MinStack() creates an empty stack.
  • push(val) puts the integer val on top of the stack.
  • pop() removes the value on top of the stack.
  • top() returns the value on top of the stack, without removing it.
  • getMin() returns the smallest value in the stack, without removing anything.

The input is a list of operations, applied in order to one new MinStack. The output lists what the top and getMin calls return, in order. The starter code spells these names the way your language does.

 

Example 1:

Input:
    push(-2)
    push(0)
    push(-3)
    getMin()
    pop()
    top()
    getMin()
Output: -3 0 -2
Explanation:
After the three pushes the stack holds -2, 0 and -3, with -3 on top, so the smallest value is -3.
pop() removes -3. Now 0 is on top, and the smallest value is -2 again: when the minimum leaves,
the minimum from before it arrived comes back.

Example 2:

Input:
    push(5)
    push(5)
    push(7)
    getMin()
    pop()
    pop()
    getMin()
    top()
Output: 5 5 5
Explanation: 5 is pushed twice. The two pops remove 7 and one of the 5s, but the other 5 is still in the stack, so the smallest value is still 5.

Example 3:

Input:
    push(3)
    top()
    getMin()
    push(1)
    push(2)
    top()
    getMin()
    pop()
    pop()
    getMin()
Output: 3 3 2 1 3
Explanation: The value on top (2) and the smallest value (1) can be different values. Once 2 and 1 are popped, only 3 is left, and it is the smallest again.

 

Constraints:

  • -231 <= val <= 231 - 1
  • pop, top and getMin are only called when the stack is not empty.
  • At most 2 * 105 calls in total are made to push, pop, top and getMin.

Note:

Each operation should run in O(1) time. Looking through the whole stack for its smallest value in getMin would take O(n) time per call, where n is the number of values in the stack.

STEP 1 OF 10
    Ctrl + Enter to check

    RUST · STACK

    Min Stack in Rust, explained

    Problem overview

    A stack gives values back in last-in, first-out order. The task is to build a MinStack that also reports the smallest value it currently holds. It has four operations besides new: push(val) adds an i32, pop() removes the top value, top() returns the top value, and get_min() returns the smallest value in the stack. Every operation must run in constant time. The calls to pop, top and get_min only happen on a non-empty stack, so the code doesn't have to handle an empty one.

    In the first example the values -2, 0 and -3 are pushed, so get_min() is -3. After pop() removes -3, top() is 0 and get_min() is -2 again. The interesting part is that last answer: when the minimum leaves the stack, the structure has to remember what the minimum was before it arrived.

    Approach: remember the minimum at every depth

    The simplest get_min scans the whole vector for its smallest element. That costs O(n) per call, which the lesson forbids. Keeping a single variable min doesn't work either: it is easy to update on a push, but after a pop there is nothing to tell what the next smallest value is.

    The way out is an observation about stacks. A value can leave only after every value above it has left, so the values below it don't change while it is in the stack. The smallest value from the bottom up to any given position is therefore fixed at the moment that position is pushed, and it can be written down once.

    So the struct keeps two vectors of the same length. stack holds the values. min_stack holds, at each depth, the smallest value from the bottom up to that depth. A push computes its entry from the previous top of min_stack with one comparison, and a pop removes the top of both vectors. The minimum of the whole stack is always the top entry of min_stack.

    Here are both vectors during the first example (the end of each vector is the top):

    push -2   stack [-2]          min_stack [-2]
    push 0    stack [-2, 0]       min_stack [-2, -2]
    push -3   stack [-2, 0, -3]   min_stack [-2, -2, -3]
    pop       stack [-2, 0]       min_stack [-2, -2]
    

    After the pop, top() returns 0 and get_min() returns -2, with no search at all. The pop uncovered the entry that was already there.

    A common alternative stores only the values that set a new minimum, in a smaller second stack. It saves some memory on average, but it needs care with duplicates. This lesson pushes one entry per value, which is simpler and always correct.

    Step by step

    1. Declare the stack field as a Vec<i32>. A vector adds, reads and removes at its end in constant time, so the end is the top of the stack.
    2. Declare the min_stack field, also a Vec<i32>. Its entry at each depth is the smallest of the value at that depth and everything below it.
    3. Write new so that it returns a MinStack holding two empty vectors. The struct expression is the last thing in the function and has no semicolon, so it is the return value.
    4. In push, add val to stack. The value goes on the ordinary stack as it would on any stack.
    5. Start current_min at val, declared with let mut. If the stack was empty, val is its only value and so its smallest.
    6. If min_stack isn't empty, lower current_min to the smaller of itself and the top of min_stack. One comparison is enough because that top already summarizes everything below.
    7. Push current_min onto min_stack. Both vectors now have the same length. Equal values are pushed too, so a minimum that was pushed twice is remembered twice.
    8. In pop, pop both vectors. They must stay the same size. Removing the top of min_stack uncovers the minimum from before the removed value arrived.
    9. In top, return the last element of stack without removing it.
    10. In get_min, return the last element of min_stack in the same way.

    The solution in Rust

    struct MinStack {
        stack: Vec<i32>,
        min_stack: Vec<i32>,
    }
    
    impl MinStack {
        fn new() -> MinStack {
            MinStack { stack: Vec::new(), min_stack: Vec::new() }
        }
    
        fn push(&mut self, val: i32) {
            self.stack.push(val);
            let mut current_min = val;
            if !self.min_stack.is_empty() {
                current_min = current_min.min(*self.min_stack.last().unwrap());
            }
            self.min_stack.push(current_min);
        }
    
        fn pop(&mut self) {
            self.stack.pop();
            self.min_stack.pop();
        }
    
        fn top(&self) -> i32 {
            *self.stack.last().unwrap()
        }
    
        fn get_min(&self) -> i32 {
            *self.min_stack.last().unwrap()
        }
    }
    

    The Rust details worth a closer look:

    • last() returns an Option<&i32>. It is None for an empty vector and a reference to the final element otherwise. unwrap() takes the reference out, which is safe here because the lesson only calls top and get_min on a non-empty stack. The leading * reads the i32 behind the reference, which is what the function returns.
    • pop() returns an Option too. The call self.stack.pop(); drops the removed value, which is fine because this lesson's pop returns nothing. On an empty vector it returns None and does nothing.
    • min is a method on integers. current_min.min(other) returns the smaller of the two i32 values.
    • &mut self and &self. push and pop change the struct, so they take &mut self. top and get_min only read it, so &self is enough, and the compiler enforces the difference.
    • Final expressions. MinStack { ... }, *self.stack.last().unwrap() and the other returned values have no semicolon. Adding one would turn the expression into a statement, and the function would no longer return its value.

    Complexity

    Time: O(1) per operation. push does two vector pushes (amortized constant time) and one comparison. pop does two vector pops. top and get_min read one element. None of them loops over the stack.

    Space: O(n). The two vectors each hold one number per value on the stack, so the memory is twice what a plain stack needs, which is still linear in the number of values.

    Edge cases and common mistakes

    • Duplicate minimums. After pushing 5, 5 and 7, min_stack is [5, 5, 5]. Two pops leave a stack of one 5, and get_min() is still 5. A design that stored a minimum only when it was strictly smaller would lose the second 5 and answer wrongly here.
    • Top and minimum differ. After pushing 3, 1 and 2, top() is 2 and get_min() is 1. After two pops only 3 is left, and get_min() is 3.
    • Forgetting to pop min_stack. If pop removes only from stack, the two vectors fall out of step and get_min keeps reporting a value that has left the stack.
    • Extreme values. Values are i32, and the code only compares them and never adds or subtracts, so nothing can overflow. Pushing i32::MIN and then i32::MAX gives get_min() equal to -2147483648 and top() equal to 2147483647.
    • Scanning in get_min. Calling self.stack.iter().min() works but costs O(n) per call, which is the cost this lesson avoids.

    Testing

    • Push -2, 0, -3, then get_min, pop, top, get_min: the results are -3, 0, -2.
    • Push 5, 5, 7, then get_min, pop, pop, get_min, top: the results are 5, 5, 5.
    • Push 3, then top and get_min: both are 3.
    • Push 3, 1, 2: top is 2 and get_min is 1. After two pops, get_min is 3.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free