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

题目描述:

        题目难度:中等

        设计一个支持 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
  • poptop 和 getMin 操作总是在 非空栈 上调用
  • pushpoptop, 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的一些知识。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值