Problem overview
A stack gives values back in reverse order: the last value pushed is the first one popped. This lesson asks for a MinStack, a stack that can also report the smallest value it currently holds. Every operation must take constant time: Push, Pop, Top and GetMin. In Go the names are capitalised, and Constructor creates an empty stack.
Take the first example. After Push(-2), Push(0) and Push(-3) the stack holds -2, 0 and -3, with -3 on top, so GetMin() returns -3. After Pop() the top is 0, and GetMin() returns -2 again. The minimum that left the stack takes its effect with it, and the earlier minimum comes back.
The calls to Pop, Top and GetMin only happen on a non-empty stack, so the code doesn't need to handle an empty one.
Approach: remember the minimum at every depth
A plain slice already gives O(1) push, pop and top. The only slow operation is GetMin: scanning the whole stack for its smallest value costs O(n) per call.
The way out comes from one observation. A value leaves the stack only after every value above it has left, so the values below it never change while it is there. The minimum of "this value and everything under it" is therefore fixed at the moment the value is pushed. It can be computed once, from one comparison, and stored.
So the stack keeps two slices of the same length. stack holds the values. minStack holds, at each depth, the smallest value from the bottom up to that depth. The last entry of minStack always describes the whole stack as it is now, which is exactly what GetMin must return.
Running the third example gives this picture. After Push(3), Push(1) and Push(2), stack is [3 1 2] and minStack is [3 1 1]. The top value is 2 and the smallest is 1, which shows that the two answers can differ. After two pops only 3 remains, and GetMin returns 3.
Step by step
- Add the
stackfield to the struct as a[]int. The end of the slice is the top of the stack, becauseappendand reslicing both work at the end. - Add the
minStackfield, also a[]int. Its entry at each depth is the smallest of the value at that depth and all values below it. - Return an empty
MinStack{}fromConstructor. Both slices start asnil, andappendandlenwork on anilslice. - In
Push, append the value tothis.stackand store the result back, sinceappendreturns the longer slice and leaves the one you gave it alone. - Start
currentMinatval. If the stack was empty, the new value is its only value, so it is also the smallest. - If
minStackisn't empty, lowercurrentMinto the smaller of itself and the last entry ofminStack. That entry is already the minimum of everything below the new value, so one comparison is enough. - Append
currentMintominStack. Now both slices have the same length. Equal values are pushed too, so a minimum that was pushed twice is remembered twice. - In
Pop, cut the last element off both slices withs[:len(s)-1]. Removing the last entry ofminStackuncovers the minimum the stack had before that value arrived. - In
Top, return the last element ofstack. Reading it removes nothing. - In
GetMin, return the last element ofminStack. That is O(1), like every other operation.
The solution in Go
type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{}
}
func (this *MinStack) Push(val int) {
this.stack = append(this.stack, val)
currentMin := val
if len(this.minStack) > 0 {
currentMin = min(currentMin, this.minStack[len(this.minStack)-1])
}
this.minStack = append(this.minStack, currentMin)
}
func (this *MinStack) Pop() {
this.stack = this.stack[:len(this.stack)-1]
this.minStack = this.minStack[:len(this.minStack)-1]
}
func (this *MinStack) Top() int {
return this.stack[len(this.stack)-1]
}
func (this *MinStack) GetMin() int {
return this.minStack[len(this.minStack)-1]
}
The Go details worth a closer look:
- Pointer receivers.
PushandPopassign tothis.stack, so they need the receiver*MinStack. With a value receiver they would change a copy and the caller's stack would stay the same. Go lets you call these methods on an addressable variable such ass := Constructor()with plains.Push(4). thisis only a name. Go has no specialthiskeyword. It is an ordinary receiver name, and the lesson uses it to match the usual shape of this problem.- The zero value works. A struct whose fields are
nilslices is a valid empty stack. Evenvar e MinStackfollowed bye.Push(4)works, ande.Top()then returns 4. appendmust be assigned back. Writingappend(this.stack, val)alone would discard the result, and the stack would not grow.- Reslicing doesn't free memory.
s[:len(s)-1]shortens the length but keeps the same backing array. That is fine here: the nextappendreuses the slot. It's the reason popping is O(1) with no copying. - Built-in
min. Since Go 1.21,minis built in, so no import is needed. It works onintdirectly.
Complexity
Time: O(1) per operation. Push does one comparison and two appends (amortised O(1): an append occasionally grows the backing array, but the cost spreads over many pushes). Pop, Top and GetMin each do a single index or reslice.
Space: O(n) for n values currently in the stack. The second slice doubles the memory but doesn't change the order of growth.
Edge cases and common mistakes
- Keeping one running
minvariable. It breaks on the first pop of the minimum: after popping -3 in the first example, a single variable still says -3, and it has no way to know the answer should be -2. - Not pushing equal values onto
minStack. In the second example 5 is pushed twice. If only the first 5 were recorded inminStack, the two slices would have different lengths, and the pops would remove the wrong minimum or run off the end ofminStack. Recording an entry per value keeps them aligned. - Popping only one slice. The slices must stay the same length. If
Poptrims onlystack,GetMinkeeps returning an old minimum. - Dropping the assignment on
append. The slice you pass is unchanged, so the value is lost. - Calling
Popon an empty stack. It panics withslice bounds out of range [:-1]. The lesson's tests never do this, but a stack you reuse elsewhere might want a check. - Values at the edges. The constraints allow -2147483648 to 2147483647, and Go's
intis 64 bits, so nothing overflows. Even-9223372036854775808works as a stored value.
Testing
Running the lesson's examples on the code gives these answers.
- Push -2, 0, -3, then
GetMin()returns -3. AfterPop(),stackis[-2 0],minStackis[-2 -2],Top()returns 0 andGetMin()returns -2. - Push 5, 5, 7, then
GetMin()returns 5. After two popsstackis[5], and bothGetMin()andTop()return 5. - Push 3, 1, 2:
Top()returns 2 andGetMin()returns 1, withminStackequal to[3 1 1]. After two pops,GetMin()returns 3.
