算法题解记录23+++最小栈(百日筑基)

题目描述:

题目难度:中等

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 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
  • poptopgetMin 操作总是在 非空栈 上调用
  • push, pop, top, and getMin最多被调用 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的一些知识。

我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

相关推荐
Kalika0-035 分钟前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
sp_fyf_20241 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
我是哈哈hh3 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
Tisfy3 小时前
LeetCode 2187.完成旅途的最少时间:二分查找
算法·leetcode·二分查找·题解·二分
Mephisto.java3 小时前
【力扣 | SQL题 | 每日四题】力扣2082, 2084, 2072, 2112, 180
sql·算法·leetcode
robin_suli3 小时前
滑动窗口->dd爱框框
算法
丶Darling.3 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5203 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法
jiyisuifeng19914 小时前
代码随想录训练营第54天|单调栈+双指针
数据结构·算法
꧁༺❀氯ྀൢ躅ྀൢ❀༻꧂4 小时前
实验4 循环结构
c语言·算法·基础题