问题描述:
设计一种数据结构来存储数字,存储数值的范围是 [-100000, 100000]。
支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
Leecode链接: https://leetcode.com/problems/min-stack/。
主要的难点在于getMin()如何实现?(尽可能让空间复杂度和时间复杂度降低)
例子:
push 1,push 2, 此时getMin()可以获得1. 继续push 0,此时getMin()获得0. 用pop()弹出栈顶元素后,此时getMin()获得1.
解题思路及优化
三种getMin()实现,第一种时间复杂度O(n),空间复杂度O(1)。第二种时间复杂度O(1),空间复杂度O(n)。第三种时间复杂度O(1),空间复杂度O(1),思考难度是递进的。
1. 时间复杂度O(n),空间复杂度O(1)的方法
适合编程初学者理解。思路:该数据结构内部有一个链表(数组也可以,但数组需要考虑当容量满了的扩容问题),push会将新元素连接到链表尾部,pop会将尾部元素删除。getMin()实现方式是遍历一遍该链表(数组),获取最小值并返回。
复杂度的计算:因为是遍历,所以时间复杂度O(n)。因为getMin()并没有申请新的辅助空间,所以空间复杂度O(1)。
C++代码如下:
#include<list> #include<vector> struct MinStack { std::vector<int> data;//C++中的动态数组,可以自动扩容。 void push(int value) { data.push_back(value);//直接存入元素到链表结尾 } int pop() { int returnValue = data.back();//先保存元素到一个变量 data.pop_back();//删除链表结尾元素 return returnValue; } int getMin() { int minValue=data.front();//设置minValue的值为第一个元素值 for (int i = 0; i < data.size();i++) {//遍历数组,此处方法不限只要遍历即可 minValue = std::min(data[i],minValue);//如果当前值比记录的minValue小,就把当前值赋给minValue //minValue = data[i] <minValue ? data[i] : minValue; 如果不想用min函数,也可以用三元表达式。功能等于上一行的代码 } return minValue; } int top(){ return data.back(); } };
1. 时间复杂度O(1),空间复杂度O(n)的方法
适合编程入门者理解。思路:该数据结构内部有两个栈(std::stack即可),使用设计模式的装饰器模式包装第一个栈的push和pop方法。此外每次push时,在第二个栈中放入此时容器中的最小值,每次pop时,在第二个栈中也弹出一个值。这样可以保证访问getMin()时,第二个栈最外的值就是最小数量。
如图:
因为只需要比较新放入元素和之前栈顶的元素谁小,放入谁即可。(放入之前的栈顶元素就是放入之前的最小值)
复杂度的计算:需要一个额外的栈,存储的元素数量和第一个栈元素数量相同,所以空间复杂度为O(n)。因为每次获取直接获得栈的最外元素即可,所以时间复杂度为O(1)
C++代码如下:
#include<stack> #include<algorithm>//用到该头文件里边的min函数 struct MinStack { std::stack<int> data; std::stack<int> storeMin; void push(int value) { data.push(value); if (storeMin.empty() == true) {//如果之前没有元素,就无法拿出第一个元素比较 storeMin.push(value);//此时直接放入即可 } else { int minValue = std::min(storeMin.top(), value);//判断一下之前的最小值小还是新放入的值小 storeMin.push(minValue); } } int pop() { int tem=top(); data.pop(); storeMin.pop(); return tem; } int getMin() { return storeMin.top(); } int top(){ return data.top(); } };
3. 时间复杂度O(1),空间复杂度O(1)的实现
适合编程进阶的人来理解。思路:一共需要一个栈和一个变量。变量是当前的最小值。栈中存储的是和放入元素时和最小值的差值。这样虽然没直接存储每个元素,但其实可以随时计算出栈顶元素。pop时可以根据栈顶的差值和最小值计算出pop后应有的最小值。
复杂度的计算:只用到了一个额外的变量用于存储最小值,所以空间复杂度是O(1),并且每个操作都是根据栈顶元素和最小值进行计算就能得到,所以时间复杂度也是O(1)。
如图:
push的过程: 从头开始一步一步的讲:
1. 放入第一个元素3时,因为是第一个元素,可以特殊处理,最小值为3,该元素和最小值3的差(3-3=0)是0,所以栈存入0.
2.放入第二个元素4时,先考虑栈的存储:此时最小值为3,该元素减最小值(4-3)得1,所以栈存入1;最小值3和元素4比较,发现最小值不变,所以最小值仍为3
3.放入第三个元素2时,先考虑栈的存储:此时最小值为3,该元素减最小值(2-3)得-1,所以栈存入-1;最小值3和元素2比较,发现最小值应为新的最小值,所以最小值设置为2
4.放入第四个元素5时,先考虑栈的存储:此时最小值为2,该元素减最小值(5-2)得3,所以栈存入3;最小值2和元素5比较,发现最小值不变,所以最小值仍为2
4.放入第五个元素1时,先考虑栈的存储:此时最小值为2,该元素减最小值(1-2)得-1,所以栈存入-1;最小值2和元素1比较,发现最小值应为新的最小值,所以最小值设置为1
放入栈的元素和最小值的变换过程如上,下面阐述如何根据栈中存储的差值来计算出原来的元素。
top的过程(获得栈顶元素,因为我们存储的是差值所以需要一步转换):
情况1:如果栈中存储的是正数,说明放入的元素比最小值大,并且最小值肯定没有因为放入这个元素而改变。所以放入的元素就是 (最小值+差值)。比如图中第四个元素,
3+2,可以得到这个元素是5。
情况2:如果栈中存储的是0,和正数情况同理,放入的元素就是(最小值+0)。
情况3:如果栈中存储的是负数,说明放入的元素比最小值小,导致因为放入这个值,最小值发生了改变。说明这个元素就是这个最小值。如图第三个元素放入时栈顶值为-1:
此时这个元素和最小值相同,值是2.
pop的过程:
pop时主要考虑,弹出了一个元素,最小值如何维护成正确的最小值。我们可以根据弹出元素和最小值来知道上一次最小值应该是多少。
情况1:弹出元素如果是正数,比如图中情况,我想弹出第四个,我怎么知道弹出最后最小值应该是多少。因为差值是正数,说明放入第四个元素时,第四个元素比当时的最小值大3,既然插入的值比最小值大,那么最小值肯定不会因为插入的这个值而改变,所以最小值仍为2.
情况2:弹出元素如果是0,和正数情况同理,说明新放入的元素正好和最小值相同,所以也不会改变最小值。
情况3:弹出元素如果是负数,如图,说明放入这个元素时,这个元素比当时的最小值小,从而改变了最小值。这个元素比当时的最小值小多少呢,因为放入的是差值所以小,所以公式为:(插入元素-当时的最小值=差值) => (当时的最小值=插入元素-差值)。只需要用这个元素的值减去差值即可,而该元素的值恰好就是后来的最小值,所以最终公式为:
当时的最小值=此时的最小值-差值 ,如下图2-(-1)=3
C++代码如下:
#include<stack> #include<algorithm>//用到该头文件里边的min函数 struct MinStack { std::stack<long long> data; long long MinValue; void push(long long value) { if (data.empty() == true) {//如果栈为空说明放入的第一个元素,特殊处理,直接放入0 data.push(0); MinValue = value; } else { data.push(value - MinValue); MinValue = std::min(value, MinValue); } } long long pop() { auto ReturnValue = top();//先获得此时的栈顶元素,一会当返回值用。这步调用的是成员函数top,和data.top有区别 if (data.top() >= 0) { MinValue = MinValue;//其实就是不变,本行也可以不写,写了也会被编译器优化掉 } else {//如果是负数的话 MinValue -= data.top();//MinValue会发生改变,data.top()存储的是一个差值,和this->top()有区别 } data.pop(); return ReturnValue; } long long top() {//通过计算得到当前栈顶在push时传入的数值。 if (data.top() >= 0) { return MinValue + data.top(); } else {//如果最顶是负数,说明当前的最小值就是top的值 return MinValue; } } long long getMin() { return MinValue; } };
有问题和错误欢迎在评论区提出和指正,原创文章,转载请注明出处,谢谢大家的阅读~