题目描述:
题目难度:中等
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
-2^31 <= val <= 2^31 - 1
pop
、top
和getMin
操作总是在 非空栈 上调用push
,pop
,top
, andgetMin
最多被调用3 * 10^4
次
解题准备:
1.基本原理:栈的本质是特殊的数组,具有先进后出的性质,我们想实现一个最小栈,最先要实现栈。
2.题意:题目要求实现一个具有特殊操作的栈,这个特殊操作是常数时间内得到最小值。
3.基本操作:如果用数组实现一个栈,需要涉及数组的增删查;
4.最小值的基本思路:想拿到最小值,如果按朴素的方法,应该是遍历一遍栈,然后返回数据,不过太慢了,不满足常数时间。对于快速使用一个常用的数据,我们解决的方法,一般是提供变量A,保存这个数据,每次调用,只需返回A即可。
解题思路:
基本操作---实现栈:
实现栈,是实现最小栈的基础,在此用数组的方式实现栈,代码如下:
class Stack {
private List<Integer> data;
// 初始化数据结构,ArrayList是可变长数组,也可以用int[],不过申请内存还是比较麻烦
public MinStack() {
data = new ArrayList<Integer>();
}
// 压栈操作
public void push(int val) {
data.add(val);
}
// 出栈操作,这里用void是因为题目也用void
public void pop() {
data.remove(data.size()-1);
}
// 得到栈顶元素,也就是刚进来的元素
public int top() {
return data.get(data.size()-1);
}
}
学会了栈的实现,就能开始处理问题了。
朴素的思路---遍历栈得到最小值:
按最简单的思想,从一堆元素里,拿到最小值,只需遍历一遍,即可。
int temp = Integer.MAX_VALUE; // 初值是int最大值即2^32-1
for(int i=0; i<data.size(); i++){
temp = Math.min(temp,data.get(i));
}
代码类似上文,比较简单,在此不赘述。
优化思路---存储栈的最小值:
对于常用的数据,我们大可用一个变量X,存储这个数据,当别人调用时,返回X即可,这是常用的思路,我们考虑其可行性。
第一,X要存储什么数据?数据是否非常大,以至于存储不了?
显然,题解最小值的范围在-2^32到2^32-1之间,用int可以存储。
第二,X有没有可能会改变?
明显,当进行push、pop操作时,最小值可能会变化。【push一个更小的数,或者pop时正好删除最小值】
第三,X改变了,那原值就不能用了,该怎么办?
提供更新机制,X能保持更新。
第四,更新机制是什么?能否具体说明?
已知,最小值只会在push、pop时出现变化,针对二者处理:
1.push:判断最小值与新来的val谁更小,X就是谁。
2.pop:判断删除的是否是最小值,如果是,则遍历一遍数组,再次得到最小值X。
第五,pop操作,如何确定删除的是否是最小值?因为数组中可能存在重复元素。
这也是本题的难点所在【虽然题解用的不是这个方法】,我的处理方法是,用另一个变量ptr,存储最小值下标,由于pop操作,只会删除下标为data.size()-1的元素【以ArrayList来看】,所以看ptr与data.size()-1是否一致,如果一致,说明删除了最小值,否则不必更新。
第六,如果栈中没有最小值【即栈为null】,该怎么办?
按理说,栈至少要有1个元素,否则它调用pop、top、getMin方法都会出错,不过为了模拟真实应用环境,假设出现了这种情况,那么我们要设计异常机制。
当栈中无元素时,最小值下标ptr指向-1,最小值为Integer.MIN_VALUE(也就是2^32-1)一般返回这个数,就说明出错。【当然,我建议用异常机制,把异常抛给调用者处理】
至此,该思路基本讲解完成,我们来查看题解的思路。
题解思路---辅助栈:
我们知道,栈的特性是先进后出,对于元素A,只要不执行pop操作,无论执行多少次push、top或者getMin,元素A始终在栈中。
考虑一个问题,如果元素A,是第一个压栈的元素,并且,A足够小【至少比接下来push的元素小】,那么,最小值会在什么情况下改变?
明显,只有pop操作可能改变最小值【因为把A删除了】。
接着思考,需要几次pop操作,才能删除A呢?
如果将A压栈后,没有push操作,那么只需1次pop。
如果将A压栈后,执行1次push操作,那么需要2次pop。
如果将A压栈后,执行2次push操作,那么需要3次pop
......
可以看出,pop的执行次数,与push的执行次数有关。
结合"存储变量,以保证读取常用数据的效率"的思想,我们不难想到,可以用两个变量X、count。初始化X=A,count=1;【假设栈中仅有元素A】
在每次push时,count++;
在每次pop时,count--。
这样,至少能保证快速得到最小元素。
问题来了。
第一,push的元素是理想的,现实中,可能新的val>A,该怎么处理?
直观地,改变X=新val,则引起第二个问题:
第二,如果改变X=新val,那么count要不要改呢?【假设元素序列为:A,1,2,3,新val】
不改:当pop操作,把新val删除后,最小值X已经失真了【此时序列:A,1,2,3】
改:令count=1,那么pop操作后,最小值X是谁?按照初设思想,此时认为栈中是没有元素的。
第三,如果改变count,并且在pop操作后,进行一次遍历,得到最小值为A,那么count此时又该怎么得到?【length-A的下标?】
我们没法忽略这些问题,而且也很难找到正确的方法,来简单地处理这个问题。
因此,我们可以认为,应该是数据结构出了问题。
干脆换一种数据结构。
我们要求,这种新的数据结构,既要能得到最小值,又要提供最小值的次数
呼之欲出了:二维数组。
int[][] matrix = new int[X][2]。。【X表示长度不确定】
对于序列【A,1,2,3,新val】,我们可以存储:
matrix[0][0] = A, matrix[0][1] = 4。
然后,matrix[1][0] = 新val, matrix[1][1] = 1。
并且,只要设置matrix[k][1] = -1,就能表示matrix[k][0]不是最小值。
不过,对于这个结构,行多少【即X多大】比较合适呢?
不好解决。
所以,又想出一个新结构:辅助栈。
辅助栈本质也是栈,只是用作辅助。
对于序列【A,1,2,3】
我们知道,最小值是A,count是4,如果用栈【或数组】表示,正好是:
序列【A,A,A,A】
对于push操作,如果新val比A小,那么序列就是【A,1,2,3,新val】,则数组:
【A,A,A,A,新val】
此时最小值是val。
我们可以发现,用栈来模拟这个操作,正好。
已知push、pop可能改变最小值,那么以二者说明:
1.push操作:如果没改变最小值,那么序列就是栈顶元素+1。
【A,A,A,A,新val,新val】
2.push操作:如果新元素γ改变最小值,那么序列就是push一个γ
【A,A,A,A,新val,γ】
3.pop操作:pop操作被化简了,只需删除辅助栈的栈顶元素即可,无需其它操作。
从化简的角度看,双变量X、ptr化简的是push操作,辅助栈化简的是pop操作,至于哪个操作化简后,得到的效果最好,则看情况了。
解题难点分析:
无
代码---存储最小值:
class MinStack {
private List<Integer> data; // 栈
private int minData; // 最小值
private int min_ptr; // 最小值下标
// 初始化
public MinStack() {
data = new ArrayList<Integer>();
min_ptr = -1;
minData = 0;
}
// push操作
public void push(int val) {
data.add(val); // 元素添加
// 此时栈中元素仅1个,即新的val
if(min_ptr==-1){
minData = val;
min_ptr = 0;
}else{
// 与原最小值做判断
if(val<minData){
minData = val;
min_ptr = data.size()-1;
}
}
}
// pop操作
public void pop() {
if(data.size()==1){
// 此时栈中将无元素
minData = 0;
min_ptr = -1;
}else{
// 判断删除的是否最小值
if(min_ptr==data.size()-1){
minData = Integer.MAX_VALUE;
// 遍历得到新的最小值
for(int i=0; i<data.size()-1; i++){
if(data.get(i)<minData){
minData = data.get(i);
min_ptr = i;
}
}
}
}
// 删除
data.remove(data.size()-1);
}
public int top() {
return data.get(data.size()-1);
}
public int getMin() {
return minData;
}
}
代码---辅助栈:
在此不提供,是力扣155题的题解,感兴趣可以去了解。
以上内容即我想分享的关于力扣热题23的一些知识。
我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。