【栈与队列】力扣155:最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
示例:

输入:
["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.

要做出这道题目,首先要理解栈结构先进后出的性质。
对于栈来说,如果一个元素 a 在入栈时,栈里有其它的元素 b, c, d,那么无论这个栈在之后经历了什么操作,只要 a 在栈中,b, c, d 就一定在栈中,因为在 a 被弹出之前,b, c, d 不会被弹出。
因此,在操作过程中的任意一个时刻,只要栈顶的元素是 a,那么就可以确定栈里面现在的元素一定是 a, b, c, d。

  1. 辅助栈和数据栈同步
    可以在每个元素 val 入栈时把当前栈的最小值 min 存储起来。在这之后无论何时,如果栈顶元素是 val,就可以直接返回存储的最小值 min。
  • 算法:
    只需要设计一个数据结构,使得每个元素 val 与其相应的最小值 min 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。
    • 当一个元素要入栈时,取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中
    • 当一个元素要出栈时,把辅助栈的栈顶元素也一并弹出
    • 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中
import math # 调用math模块
class MinStack:

    def __init__(self):
        self.stack = []
        self.minStack = [math.inf] # 定义辅助栈并初始化
'''
注意:如果没有初始化辅助栈,在向其push新元素时会显示list index out of range,因为初始时辅助栈内没有minStack[-1],无法比较
'''

    def push(self, val: int) -> None:
        self.stack.append(val)
        self.minStack.append(min(val, self.minStack[-1])) # 辅助栈的栈顶是压入新元素val前栈stack的最小值,因此新栈顶压入的是旧的最小值和新元素中的最小值。
'''
若定义时不初始化辅助栈,可以这样写
    def push(self, val: int) -> None:
        if not self.minStack:
            self.minStack.append(val)
        else:
            self.minStack.append(min(val, self.minStack[-1]))
'''

    def pop(self) -> None:
        # pop、top 和 getMin 操作总是在非空栈上调用
        if self.stack:
        self.stack.pop()
        # if self.minStack:
        self.minStack.pop()

    def top(self) -> int:
        # if self.stack:
        return self.stack[-1]

    def getMin(self) -> int:
        # if self.minStack:
        return self.minStack[-1]

# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()

时间复杂度:对于题目中的所有操作,时间复杂度均为 O(1)。因为栈的插入、删除与读取操作都是 O(1),所定义的每个操作最多调用栈操作两次。
空间复杂度:O(n),其中 n 为总操作数。最坏情况下会连续插入 n 个元素,此时两个栈占用的空间为 O(n)。

  1. 辅助栈和数据栈不同步
    由【辅助栈和数据栈同步】的思想,当数据栈进来的数越来越大的时候,要在辅助栈顶放置和当前辅助栈顶一样的元素,这样做有点“浪费”。基于这一点做一些“优化”,但是在编码上就要注意一些边界条件:
    • 辅助栈为空的时候,必须放入新元素
    • 新元素 <= 辅助栈栈顶元素的时候,才放入。特别注意这里“等于”要考虑进去,因为出栈的时候,连续的、相等的并且是最小值的元素要同步出栈
    • 出栈的时候,辅助栈的栈顶元素等于数据栈的栈顶元素,才出栈
      总结:出栈时,最小值出栈才同步;入栈时,最小值入栈才同步。
      对比:【同步栈】所有操作都同步进行,思路清楚,所以调试代码、定位问题也简单,不考虑空间的话其实更好一些。【不同步栈】虽然减少了一些空间,但是在“出栈”、“入栈”的时候还要做判断,也有性能上的消耗。
class MinStack:

    def __init__(self):
        self.stack = []
        self.minStack = [] # 辅助栈

    def push(self, val: int) -> None:
        self.stack.append(val)
        # 关键 1:辅助栈为空的时候,必须放入新元素
        # 关键 2:新元素 <= 辅助栈栈顶元素的时候才放入,特别注意`=`要考虑进去
        if len(self.minStack) == 0 or val <= self.minStack[-1]:
            self.minStack.append(val)

    def pop(self) -> None:
        # 关键3:出栈的时候,数据栈的元素一定出栈,而辅助栈的栈顶元素=数据栈的栈顶元素才出栈
        top = self.stack.pop()

        if self.minStack and top == self.minStack[-1]:
            self.minStack.pop()
        return top # 为什么这一句不能放在line 17呢???

    def top(self) -> int:
        if self.stack:
            return self.stack[-1]

    def getMin(self) -> int:
        if self.minStack:
            return self.minStack[-1]

时间复杂度:O(1),“出栈”、“入栈”、“查看栈顶元素”的操作不论数据规模多大,都只有有限个步骤。
空间复杂度:O(N),这里 N 是读出的数据的个数。
作者:liweiwei1419
链接:https://leetcode.cn/problems/min-stack/solution/shi-yong-fu-zhu-zhan-tong-bu-he-bu-tong-bu-python-/

  1. 不借助辅助栈,直接用数据栈储存元组数据
    题目要求在常数时间内获得栈中的最小值,因此不能在 getMin() 的时候再去计算最小值,最好应该在 push 或者 pop 的时候就已经计算好了当前栈中的最小值。

思路:

  • 可以用一个栈,这个栈同时保存的是【每个元素 val 进栈的时候的值】 与【插入该值后的栈内最小值】。即每次新元素 val 入栈的时候保存一个元组:(新元素 val, 栈内最小值)。
  • 这个元组是一个整体,同时进栈和出栈。即栈顶同时有值和栈内最小值,top()函数是获取栈顶的当前值,即栈顶元组的第一个值; getMin() 函数是获取栈内最小值,即栈顶元组的第二个值;pop() 函数是删除栈顶的元组。
  • 每次新元素入栈时,需要求新的栈内最小值:
    • 当栈为空,保存元组 (val, val)
    • 当栈不空,保存元组 (val, min(此前栈内最小值, val))),即比较当前新插入元素 val 和 当前栈内最小值(即栈顶元组的第二个值)的大小。
  • 出栈:删除栈顶的元组。
class MinStack:

    def __init__(self):
        self.stack = []

    def push(self, val: int) -> None:
        if not self.stack:
            self.stack.append((val, val)) # 注意括号
        else:
            self.stack.append((val, min(val, self.stack[-1][1]))) # -1 定位到栈顶,在元组中,0 是新元素,1 是当前最小值

    def pop(self) -> None:
        self.stack.pop() # 整个元组都弹出

    def top(self) -> int:
        return self.stack[-1][0] # 返回栈顶元素

    def getMin(self) -> int:
        return self.stack[-1][1] # 返回元组的第二个元素

image
可以看出复杂度是差不多的

posted @ 2022-05-19 18:25  Vonos  阅读(51)  评论(0)    收藏  举报