2025/9/8
题目(Medium):
我的思路:
这题需要实现的类需要满足两个条件:
①能够像栈一样进行操作
②能够常数时间复杂度找到最小值
其中条件①就是操作要先进后出,后进先出的意思。而条件二则暗示我们需要一直有某个东西指向最小值,以在需要的时候立马从这个指针中提取。
先想用常规栈和一个常数记录最小值,如果当前压入栈的元素比记录的最小值还小,那就更新最小值,这样看起来好像返回的时候就可以直接返回最小值了。
但是麻烦的地方在于弹出元素的时候,加入入栈的元素是{1,2,3,4,-1},我们当前知道最小值是-1,但是-1弹出了之后,我们肉眼可以看出来剩下的最小值是1。但是如果是栈的话,我们不可能直接去摸到这个栈底的元素,而需要一个能够直接-1弹出后指向1的指针。因此我们可以考虑用链表来实现。
如果用链表的话:
- 我们可以维护一个topNode指针,每次入栈元素加入后就更新指向它,每次弹出元素就回退到上一个节点位置,同样可以满足栈的操作规则。同时可以看出要用双向链表
- 同时我们可以有一个minNode指针指向当前最小值所在的节点,而这个节点会指向第二小的节点。(入栈的时候如果新加入的节点的值比原来minNode指向的节点的值还小的话,那它才会有指向上一个最小值的指针,注意第一个加入的值它一定是没有指向上一个最小值的指针的)
具体代码如下:
struct MyListNode {
int val; //当前的值
MyListNode *next; //下一个节点
MyListNode * pre; //上一个节点
MyListNode *lastMin; //上一个最小值节点位置
MyListNode(int x) : val(x), next(nullptr), pre(nullptr), lastMin(nullptr) {}
};
class MinStack {
private:
MyListNode* stack; //链表模拟栈
MyListNode* topNode; //顶部指针
MyListNode* minNode; //当前最小值节点指针
public:
MinStack() {
cout << "最小栈初始化" <<endl;
topNode = stack = new MyListNode(INT_MAX);
minNode = nullptr;
}
void push(int val) {
//常规栈:先进后出,后进的在顶上
MyListNode* newNode = new MyListNode(val);
//入栈
topNode->next = newNode;
newNode->pre = topNode;
topNode = topNode -> next;
if(minNode == nullptr){
//说明此时栈中没有元素(那当然也没有最小元素了)
minNode = newNode;
}
else{
//否则和存在的最小节点进行比较
if(val <= minNode->val){
//更新最小节点位置
newNode->lastMin = minNode;
minNode = newNode;
}
}
}
void pop() {
//判断当前是否要更新最小值节点
if(topNode->lastMin != nullptr)
minNode = topNode->lastMin;
//然后把该节点从链表中断开
//如果当前最小值节点 就是要弹出的头节点 说明此时已经是栈中最后一个元素了
if(minNode == topNode)
minNode = nullptr;
topNode = topNode->pre;
delete topNode->next;
topNode->next = nullptr;
}
int top() {
return topNode->val;
}
int getMin() {
return minNode->val;
}
};
时间复杂度:push,top,getMin,pop四个操作的时间复杂度都是O(1)
空间复杂度:O(N):取决于有多少个元素要入栈
更好的思路:
其实也可可以真的用栈的,只是要用两个栈:
- 一个栈负责正常地入栈出栈
- 另一个栈同步进行入栈出栈,只是入栈的时候的值一直只加入当前最小的值。同时要获取最小值也是在这个栈中获取
具体代码如下:
class MinStack {
private:
stack<int> mainStack;
stack<int> minStack;
public:
MinStack() {
cout << "最小栈初始化" <<endl;
minStack.push(INT_MAX);
}
void push(int val) {
mainStack.push(val);
minStack.push(min(minStack.top(), val)); //最小值栈一直入的是当前最小的值
}
void pop() {
mainStack.pop();
minStack.pop();
}
int top() {
return mainStack.top();
}
int getMin() {
return minStack.top();
}
};
时间复杂度:push,top,getMin,pop四个操作的时间复杂度都是O(1)
空间复杂度:O(N):取决于有多少个元素要入栈
总结:
①对于要快速指定和查找元素的需求,考虑有灵活指针的链表是可以的
②对于这里的栈的需求,用的是一个主栈和一个辅助栈,主栈负责正常操作,辅助栈在正常操作的前提下,对入栈的时候的元素进行了一些大小判断。然后这两个栈同步操作,才能达到我们需要的功能