题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
这个题目的意思就是我们的函数里边有一个min函数,当我们调用这个函数的时候,不需要去遍历我们的栈,直接调用这个函数就可以返回我们栈里边的最小元素。
那我们就一步一步的来分析一下这个问题。
第一步:设置一个min变量
这是很好理解的,我们现在定义一个变量,int类型的min,然后开始操作我们的栈,当栈是空的时候, 入栈操作的时候就把min也给赋值了。
class Solution {
public:
void push(int value) {
if (s.empty()||value<mini)
{
s.push(value);
mini = value;
}
else{
s.push(value);
}
}
void pop() {
s.pop();
//这时候就很复杂了,可能最小的值被pop出去了
//这时候就需要遍历整个栈来找出目前的最小值
}
int top() {
return s.top();
}
int min() {
return mini;
}
private:
int mini;
stack<int> s;
};
这里通过一段简单的代码来实现一下,这里pop的时间复杂度就是O(n)了,因为每次pop都有可能把最小值给pop出去了。但是每次获取mini的时间仍然是O(1)。当然这自然不是最好的办法。
第二步:通过一个辅助栈来实现
这个方法是大多数人都了解的方法,就是我们创建一个新的栈,这个栈专门用来存储最小值,这样的话即使pop也无所谓了。所有操作的时间复杂度都是O(1),不过这种方法占用的空间就大了一些些是O(n)了。
class Solution {
public:
void push(int value) {
if (mini.empty()||value<=mini.top())
{
mini.push(value);
s1.push(value);
}
else
{
s1.push(value);
mini.push(mini.top());
}
}
void pop() {
if (s1.empty())
return ;
s1.pop();
mini.pop();
}
int top() {
if (s1.empty())
return -1;
return s1.top();
}
int min() {
if (s1.empty())
return -1;
return mini.top();
}
private:
stack<int> s1;
stack<int> mini;
};
这也是我提交的代码,通过了牛客网的所有测试,但是这里的代码并不是很好。首先就是我们没有判断当栈是空的时候仍然进行pop、top、min等一系列操作应该怎么办。这里我之后进行了一点点的改进。加了
if (s1.empty())
return -1;
两句话来判断,如果我们的栈是空的话,就返回-1.
第三步语法优化:
这样自然还是会出现问题,如果我们的数据里边曾经push过-1,怎么办?那岂不是出现了乌龙,我明明是返回一个值,你缺判断成了错误码。
这里我们既然学习了C++的异常,我们就要学会使用,异常是C++里出现的很好的一个机制。
Throw exception(1,”栈为空”);这样的话就不存在你的特殊返回值在push阶段操作过了。
第四步算法优化:
说了我们代码上的问题,那这个辅助栈是不是最好的?有没有优化的可能?自然是有!虽然不能在时间复杂度上进行优化,因为时间复杂度已经是O(1)了再优化就不需要运行直接获得结果了!那这里我们可以对我们的空间复杂度进行一个小小的优化。
假如我们的元素插入的是2,1,9,9,9,8,7,6,7这样的话我们的最小栈里边存的元素不就是2,1,1,1,1,1,1,1,1后边全都是重复的1.大量的重复,那我们是不是可以在这个上边做一点文章。
那我们是不是可以这样,当我们进行插入操作的时候我们进行判断,如果我们现在插入的元素比我们的最小值要小我们就把他插入进去,如果说比我们的最小值大,那我们的最小栈就不操作
![](https://i-blog.csdnimg.cn/blog_migrate/b4f105c20d5944c84d5bdda2ac31b406.png)
当我们插入元素的时候就和最小栈顶部元素进行比较如果说比他小的话再进最小栈,
![](https://i-blog.csdnimg.cn/blog_migrate/ec2eeb77679de5718b6050d62db2d3e7.png)
这时候再来一个2,2比我们最小栈的栈顶元素小,那这个2就不进入我们的最小栈。这样的话当我们执行pop操作的时候也需要对比一下。
![](https://i-blog.csdnimg.cn/blog_migrate/d06952ae3326f352abfd72428c2c5f0d.png)
当进行出栈操作的时候,我们判断一下,现在出的这个元素和我们最小栈的栈顶元素相等不,如果不相等那最小栈不出,相等的话才会出。
![](https://i-blog.csdnimg.cn/blog_migrate/e33c2ff94c141989feeec4e6743ed0bc.png)
当我们的正常栈pop现在栈顶元素1的时候,发现现在出的元素和我栈顶的元素相等,那最小栈也要pop。
但是这时候就又来了一个问题,假如说你现在入的元素和最小栈栈顶元素相等的时候你入不如最小栈。
这是肯定要入的不然就会出很大的问题
![](https://i-blog.csdnimg.cn/blog_migrate/93a0121e3697ff91f24a9737531ff20a.png)
这时候我们pop一个元素,发现1和最小栈栈顶元素是一样大小的,那最小栈的元素也会出去,这时候最小栈里边剩下的元素就是2了,但是正常栈里边还有那么多1,所以相等肯定是要入栈的。
![](https://i-blog.csdnimg.cn/blog_migrate/3cbc4c3081ab092065d8adbcb0bcc2c6.png)
那我们入栈的元素特殊的话是不是又成了这样,我们的空间还是有浪费,我们这里还是可以再继续优化的。
class Solution {
public:
void push(int value) {
if (mini.empty()||value<mini.top())
{
s.push(value);
mini.push(s.size());
}
else
{
s.push(value);
}
}
void pop() {
if (mini.top() == s.size())
{
mini.pop();
}
s.pop();
}
int top() {
return s.top();
}
int min() {
//这里的索引需要stack通过数组来实现
//或者在java中存在get函数接口但是在c++的stl中
//并没有提供获取栈指定位置的接口这里就没实现
}
private:
stack<int> s;
stack<int> mini;
}
这里我们存储的是索引可以理解成下标,然后通过这个下标来访问栈的某一特定位置的元素。因为C++ STL中使用deque也就是双端队列来实现的,所以这里并没有提供函数接口,不过如果我们自己通过数组来实现一个栈的话还是可以实现我们上边的思想的,这里我就不实现了。这种编写程序的思想还是要有的。