Start learning free

LESSON 398 OF 540 · STACK

Min Stack in Kotlin: O(1) getMin with a Second Stack

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 9
    Ctrl + Enter to check

    KOTLIN · STACK

    Min Stack in Kotlin, explained

    Problem overview

    You build a class MinStack with four operations: push(value) puts a number on top, pop() removes the top number, top() returns it, and getMin() returns the smallest number currently in the stack. Every operation has to take constant time, and pop, top and getMin are only called on a non-empty stack.

    Take the first example: push -2, 0 and -3. The stack holds -2, 0, -3 with -3 on top, so getMin() returns -3. After pop() the stack is -2, 0, so top() returns 0 and getMin() returns -2. The minimum that was removed is gone, and the earlier one is back. The outputs are -3, 0, -2.

    Approach

    The plain stack operations are easy: a list that you only touch at its end gives O(1) push, pop and top. The trouble is getMin. Scanning the whole stack for the smallest value costs O(n) per call, and with up to 200,000 calls that is far too slow.

    Keeping a single variable min does not work either. When the current minimum is popped, you would need the second smallest value, and you have thrown it away.

    The fix rests on one observation: a value leaves the stack only after every value above it has left. While it is in the stack, the values below it never change. So the minimum of "this value and everything below it" can be computed once, when the value is pushed, and stored next to it. A second stack, minStack, holds exactly that: its entry at each depth is the smallest value from the bottom up to that depth. Then:

    • getMin reads the top of minStack;
    • pop removes the top of both stacks, which uncovers the previous minimum automatically.

    This costs one extra number per value, so O(n) space, and every operation stays O(1).

    Step by step

    1. Two stacks. Declare two empty ArrayDeque<Int>() properties, stack for the values and minStack for the running minimums. The end of each deque is the top of the stack.
    2. Push the value. In push, add value to the end of stack.
    3. Work out the new minimum. Start a var currentMin at value. If the stack was empty, that is already right. If minStack has entries, its last entry is the smallest of everything below, so one minOf comparison with it gives the minimum of the whole stack including value.
    4. Record it. Add currentMin to the end of minStack. Both stacks now have the same size. Equal values are pushed too, so a minimum that was pushed twice is remembered twice.
    5. Pop from both. pop calls removeLast() on stack and on minStack. They must stay the same size.
    6. Read the answers. top returns stack.last() and getMin returns minStack.last(). Neither removes anything.

    The solution in Kotlin

    class MinStack {
        val stack = ArrayDeque<Int>()
        val minStack = ArrayDeque<Int>()
    
        fun push(value: Int) {
            stack.addLast(value)
            var currentMin = value
            if (minStack.isNotEmpty()) {
                currentMin = minOf(currentMin, minStack.last())
            }
            minStack.addLast(currentMin)
        }
    
        fun pop() {
            stack.removeLast()
            minStack.removeLast()
        }
    
        fun top(): Int {
            return stack.last()
        }
    
        fun getMin(): Int {
            return minStack.last()
        }
    }
    

    Some Kotlin details that matter here:

    • ArrayDeque is Kotlin's own. Without an import, ArrayDeque is kotlin.collections.ArrayDeque, a growable array used as a double-ended queue. addLast, removeLast and last() all work at the end in O(1) (amortised for adding).
    • The parameter is called value. The task says val, but val is a keyword in Kotlin, so it cannot name a parameter.
    • last() on an empty deque throws NoSuchElementException. That is why the code checks isNotEmpty() before reading minStack.last() in push. The other methods rely on the guarantee that the stack is not empty.
    • Parameters are read-only. That is why the code copies value into a var instead of changing it.
    • val fixes the reference, not the contents. stack is a val, yet the deque still grows and shrinks.

    Tracing the first example

    The two stacks after each push (the last number is the top), and after the pop:

    push -2   stack=[-2]          minStack=[-2]
    push 0    stack=[-2, 0]       minStack=[-2, -2]
    push -3   stack=[-2, 0, -3]   minStack=[-2, -2, -3]
    pop       stack=[-2, 0]       minStack=[-2, -2]
    

    After the third push getMin() reads -3. After the pop it reads -2, with no search at all. Pushing 0 did not lower the minimum, so the entry for 0 repeats -2.

    Complexity

    Every operation does a fixed number of deque operations at the end: O(1) time each (adding is amortised O(1) because the backing array occasionally doubles). The two stacks together hold 2n numbers for n values, so the space is O(n).

    Edge cases and common mistakes

    • A single min variable. It breaks on the first pop of the minimum, because the previous minimum is lost.
    • Forgetting to pop minStack. The stacks fall out of step and getMin returns a minimum for values that are no longer there.
    • Skipping equal values. Storing a minimum only when it is strictly smaller breaks duplicates: after pushing 5, 5, 7 and popping 7 and one 5, the other 5 must still count. Here every push writes an entry, so this works.
    • Extreme values. Values range over the whole Int. Nothing is added or subtracted, only compared, so Int.MIN_VALUE and Int.MAX_VALUE work: after pushing both, getMin() returns -2147483648 and top() returns 2147483647.
    • Top and minimum differ. In the third example, after pushing 3, 1 and 2, top() is 2 while getMin() is 1. Keep the two roles separate.

    Testing

    Run the examples against your class in a main of your own. These sequences give these outputs:

    • push 5, push 5, push 7, getMin(), pop, pop, getMin(), top() gives 5, 5, 5.
    • push 3, top(), getMin(), push 1, push 2, top(), getMin(), pop, pop, getMin() gives 3, 3, 2, 1, 3.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free