155. Min Stack
題目 / Problem
設計一個堆疊(stack),它除了支援一般的 push、pop、top 操作之外,還能在常數時間 O(1) 內回傳堆疊中的最小元素。
實作 MinStack 類別:
- MinStack():初始化堆疊物件。
- void push(int val):把元素 val 推入堆疊頂端。
- void pop():移除堆疊頂端的元素。
- int top():取得堆疊頂端的元素。
- int getMin():取得堆疊中的最小元素。
你必須讓每一個操作都是 O(1) 時間複雜度。
Design a stack that supports push, pop, top, and retrieving the minimum element — all in constant time O(1).
Implement the MinStack class:
- MinStack() initializes the object.
- void push(int val) pushes val onto the stack.
- void pop() removes the top element.
- int top() returns the top element.
- int getMin() returns the minimum element currently in the stack.
Every operation must run in O(1) time.
Constraints / 限制條件:
- -2^31 <= val <= 2^31 - 1(值可能是負數,且會接近 32 位元整數的邊界)
- pop、top、getMin 只會在非空的堆疊上被呼叫。
- 最多會有 3 * 10^4 次呼叫。
Worked example / 範例:
操作: MinStack(), push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()
輸出: null, null, null, null, -3, null, 0, -2
push(-2)、push(0)、push(-3) 後,最小值是 -3;pop() 移除 -3 之後,堆疊剩下 [-2, 0],最小值變回 -2,頂端是 0。
名詞解釋 / Glossary
- 堆疊 / Stack:一種「後進先出」(LIFO, Last-In-First-Out) 的資料結構,就像一疊盤子,只能從最上面拿取或放置。最後放進去的元素,會最先被拿出來。
- 常數時間 / Constant time O(1):不論堆疊裡有多少元素,操作所花的時間都固定不變,不會因為資料變多而變慢。
- 輔助堆疊 / Auxiliary (helper) stack:除了存放實際資料的主堆疊外,額外再用一個堆疊專門追蹤「到目前為止的最小值」。這是本題的核心技巧。
- 不變量 / Invariant:一個在程式執行過程中「永遠成立」的性質。本題的不變量是:輔助堆疊的頂端,永遠等於主堆疊目前所有元素的最小值。
- 動態陣列 / Dynamic array (vector):一個可以自動增長的陣列。在 C 裡我們用
malloc/realloc手動管理,在 C++ 裡用std::vector自動管理。我們用它來實作堆疊。 std::vector:C++ 標準函式庫提供的動態陣列,push_back加到尾端、pop_back移除尾端、back()看尾端元素,剛好對應堆疊的 push/pop/top。
思路
最直覺的暴力做法是:只用一個普通堆疊存資料,每次呼叫 getMin() 時就從頭到尾掃描一遍找最小值。但這樣 getMin() 是 O(n),不滿足題目要求的 O(1)。問題的關鍵是:當我們 pop 掉一個元素後,最小值可能會改變,例如原本最小是 -3,把 -3 拿掉後最小值要「還原」成 -2,但一個普通堆疊無法記得「之前的最小值是多少」。解法是再開一個輔助堆疊 minStack,它和主堆疊同步成長與縮小:每次 push 一個值 val 時,我們把「val 和目前 minStack 頂端兩者中較小者」推入 minStack;每次 pop 時,兩個堆疊都同時 pop。這樣維持一個不變量——minStack 的頂端永遠是主堆疊現有元素的最小值。因為 minStack 的每一層都記錄了「當主堆疊到這個高度時的最小值」,所以 pop 之後最小值會自動還原成上一層的值,getMin() 只要回傳 minStack 頂端,就是 O(1)。
The brute-force idea uses one ordinary stack and scans all elements on every getMin() call — but that makes getMin() O(n), violating the O(1) requirement. The real difficulty is that popping can change the minimum: if the current min is -3 and we pop it, the min must "roll back" to -2, yet a plain stack has no memory of past minimums. The fix is a second auxiliary stack minStack that grows and shrinks in lockstep with the main stack. On every push of val, we push min(val, current top of minStack) onto minStack; on every pop, both stacks pop together. This maintains the invariant that minStack's top is always the minimum of all elements currently in the main stack. Each level of minStack records "the minimum when the main stack was this tall," so after a pop the minimum automatically reverts to the previous level, and getMin() is just reading the top — O(1).
逐步走查 / Walkthrough
我們追蹤範例:push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()。
下面用 dataStack(主堆疊)和 minStack(最小值堆疊)來表示,最右邊是頂端。
We trace the sample. dataStack is the main stack, minStack tracks minimums; rightmost = top.
| 操作 / Operation | dataStack | minStack | 回傳 / Returns | 說明 / Note |
|---|---|---|---|---|
push(-2) |
[-2] |
[-2] |
— | minStack 空,直接放 -2 / first element, min is -2 |
push(0) |
[-2, 0] |
[-2, -2] |
— | min(0, -2) = -2,推入 -2 / min stays -2 |
push(-3) |
[-2, 0, -3] |
[-2, -2, -3] |
— | min(-3, -2) = -3,推入 -3 / new min -3 |
getMin() |
[-2, 0, -3] |
[-2, -2, -3] |
-3 | 讀 minStack 頂端 / read minStack top |
pop() |
[-2, 0] |
[-2, -2] |
— | 兩個堆疊都 pop / pop both stacks |
top() |
[-2, 0] |
[-2, -2] |
0 | 讀 dataStack 頂端 / read dataStack top |
getMin() |
[-2, 0] |
[-2, -2] |
-2 | 最小值自動還原成 -2 / min rolled back to -2 |
注意 pop() 那一步:拿掉 -3 後,minStack 也丟掉頂端的 -3,頂端自動變回 -2,最小值就「免費」還原了,不需要重新掃描。
Notice the pop() step: removing -3 also drops the -3 from minStack, so its top reverts to -2 automatically — the minimum is restored "for free," no rescanning needed.
Solution — C
// 演算法:用兩個堆疊。dataStack 存實際資料,minStack 同步存「到目前為止的最小值」。
// Algorithm: two stacks. dataStack holds the data; minStack holds the running minimum.
// push 時把 min(val, 目前最小) 推入 minStack;pop 時兩者同時彈出,getMin 讀 minStack 頂端。
// On push, store min(val, current min) in minStack; pop both together; getMin reads minStack's top.
#include <stdlib.h> // malloc / free / realloc,動態記憶體 / dynamic memory
// MinStack 結構:用兩個動態陣列模擬兩個堆疊
// The MinStack struct: two dynamic arrays acting as two stacks
typedef struct {
int *data; // 主堆疊的元素陣列 / array storing the main stack elements
int *minv; // 最小值堆疊的元素陣列 / array storing the running minimums
int size; // 目前堆疊有幾個元素(也是下一個寫入位置)/ current count = next write index
int cap; // 陣列目前能容納的容量 / current allocated capacity
} MinStack;
// 建立並初始化一個 MinStack 物件
// Create and initialize a MinStack object
MinStack* minStackCreate(void) {
MinStack *s = (MinStack *)malloc(sizeof(MinStack)); // 配置結構本身的記憶體 / allocate the struct
s->cap = 16; // 先給一個小的初始容量 / start with a small capacity
s->size = 0; // 一開始是空的 / initially empty
s->data = (int *)malloc(sizeof(int) * s->cap); // 配置主堆疊陣列 / allocate the data array
s->minv = (int *)malloc(sizeof(int) * s->cap); // 配置最小值堆疊陣列 / allocate the min array
return s; // 回傳指標供後續操作使用 / return the pointer
}
// 把 val 推入堆疊
// Push val onto the stack
void minStackPush(MinStack* obj, int val) {
if (obj->size == obj->cap) { // 容量滿了就要擴充 / grow when array is full
obj->cap *= 2; // 容量加倍,分攤成本 O(1) / double capacity (amortized O(1))
// realloc 把陣列搬到更大的空間,舊資料會保留 / realloc resizes the block, keeping old data
obj->data = (int *)realloc(obj->data, sizeof(int) * obj->cap);
obj->minv = (int *)realloc(obj->minv, sizeof(int) * obj->cap);
}
obj->data[obj->size] = val; // 把 val 寫到主堆疊頂端 / write val at the top of data
// 計算新的最小值:若堆疊原本是空的,最小值就是 val;否則和前一個最小值比較取較小者
// Compute new minimum: if empty, it's val; otherwise take min(val, previous min)
if (obj->size == 0 || val < obj->minv[obj->size - 1]) {
obj->minv[obj->size] = val; // val 更小(或首個元素),記錄 val / val is smaller (or first)
} else {
obj->minv[obj->size] = obj->minv[obj->size - 1]; // 沿用前一個最小值 / carry over previous min
}
obj->size++; // 元素數量加一 / increment count
}
// 移除頂端元素
// Remove the top element
void minStackPop(MinStack* obj) {
obj->size--; // 只要把 size 減一,頂端就被「丟棄」了,無需清資料 / just decrement size to drop the top
}
// 取得頂端元素
// Get the top element
int minStackTop(MinStack* obj) {
return obj->data[obj->size - 1]; // size-1 是頂端索引 / size-1 is the top index
}
// 取得目前最小值
// Get the current minimum
int minStackGetMin(MinStack* obj) {
return obj->minv[obj->size - 1]; // minStack 頂端即為最小值,O(1) / minStack's top is the min, O(1)
}
// 釋放所有配置的記憶體,避免記憶體洩漏
// Free all allocated memory to avoid leaks
void minStackFree(MinStack* obj) {
free(obj->data); // 釋放主堆疊陣列 / free the data array
free(obj->minv); // 釋放最小值陣列 / free the min array
free(obj); // 釋放結構本身 / free the struct itself
}
/**
* Your MinStack struct will be instantiated and called as such:
* MinStack* obj = minStackCreate();
* minStackPush(obj, val);
* minStackPop(obj);
* int param_3 = minStackTop(obj);
* int param_4 = minStackGetMin(obj);
* minStackFree(obj);
*/
Solution — C++
// 演算法:用兩個 std::vector 當堆疊。dataStack 存資料,minStack 存「到此為止的最小值」。
// Algorithm: two std::vectors as stacks. dataStack holds data; minStack holds the running min.
// push 推入 min(val, 目前最小);pop 同時彈出兩者;getMin 回傳 minStack.back()。
// push stores min(val, current min); pop pops both; getMin returns minStack.back().
#include <vector> // std::vector,可自動增長的動態陣列 / a dynamic, auto-growing array
#include <algorithm> // std::min,回傳兩數中的較小者 / returns the smaller of two values
class MinStack {
private:
std::vector<int> dataStack; // 主堆疊:尾端就是頂端 / main stack; the back() is the top
std::vector<int> minStack; // 最小值堆疊:與主堆疊同步 / running-min stack, kept in sync
public:
// 建構子:vector 預設為空,不需做額外初始化
// Constructor: vectors default to empty, no extra setup needed
MinStack() {}
// 推入 val
// Push val
void push(int val) {
dataStack.push_back(val); // push_back 把 val 加到尾端(頂端)/ append val to the back (top)
// 若 minStack 為空,最小值就是 val;否則取 val 與目前最小值的較小者
// If minStack is empty, the min is val; otherwise take min(val, current min)
if (minStack.empty()) {
minStack.push_back(val); // 首個元素 / first element
} else {
minStack.push_back(std::min(val, minStack.back())); // back() 是頂端 / back() is the top
}
}
// 移除頂端
// Remove the top
void pop() {
dataStack.pop_back(); // pop_back 移除尾端元素 / remove the back element
minStack.pop_back(); // 兩個堆疊必須同步彈出 / both stacks must pop together
}
// 取得頂端元素
// Get the top element
int top() {
return dataStack.back(); // back() 回傳尾端(頂端)元素 / back() returns the top element
}
// 取得目前最小值
// Get the current minimum
int getMin() {
return minStack.back(); // minStack 頂端即最小值,O(1) / minStack's top is the min, O(1)
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
複雜度 / Complexity
- Time: O(1) — 每個操作(push、pop、top、getMin)都只做固定次數的陣列存取與一次比較,不論堆疊有多少元素都一樣。push 偶爾會觸發陣列擴充(C 版的 realloc),但因為容量加倍,平均下來仍是攤還 O(1)。 Every operation does a fixed number of array accesses and at most one comparison, independent of stack size. The C version occasionally reallocates on push, but capacity doubling makes it amortized O(1).
- Space: O(n) — n 是堆疊中元素的數量。除了主堆疊的 n 個元素,minStack 也存了 n 個值,所以總空間是 2n,仍屬 O(n)。 n is the number of elements. Besides the n data elements, minStack also stores n values, giving 2n total — still O(n).
Pitfalls & Edge Cases
- 整數溢位 / Integer overflow:值可達
-2^31與2^31 - 1,但本解法只做比較與儲存、不做加減運算,所以不會溢位。若你改用「差值編碼」等技巧就要小心int溢位 / We only compare and store values (no arithmetic), so no overflow. Tricks like delta-encoding would riskintoverflow. - 首個元素的處理 / First element:第一次 push 時 minStack 是空的,必須特別處理(直接存 val),否則去讀「前一個最小值」會存取到無效位置 / On the first push, minStack is empty; you must store val directly, or reading a "previous min" accesses invalid memory.
- 兩個堆疊必須同步 / Keep both stacks in sync:pop 時若忘記同時 pop minStack,最小值就會錯亂。每次 push 一定推一個值進 minStack,pop 也一定彈一個 / If pop forgets to pop minStack too, the min becomes wrong. Always push one value to minStack per push, and pop one per pop.
- 重複的最小值 / Duplicate minimums:例如 push 多個相同最小值時,每一層都各記一份,pop 一次只還原一層,這正是我們要的行為——不會提早把最小值丟掉 / When the min repeats, each level stores its own copy, so one pop reverts only one level — the min isn't dropped too early.
- 記憶體釋放 (C) / Freeing memory (C):C 版必須在
minStackFree釋放data、minv和結構本身三塊記憶體,漏掉任何一個都會造成記憶體洩漏 / The C version must freedata,minv, and the struct itself; missing any one leaks memory. - 空堆疊 / Empty stack:題目保證 pop/top/getMin 不會在空堆疊上呼叫,所以程式碼未額外檢查;若無此保證,需先判斷
size > 0/ The problem guarantees these are never called on an empty stack, so we skip the check; otherwise guard withsize > 0.