兰話一

博客园 首页 新随笔 联系 订阅 管理

问题描述:

设计一种数据结构来存储数字,存储数值的范围是 [-100000, 100000]。

支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
Leecode链接: https://leetcode.com/problems/min-stack/。

主要的难点在于getMin()如何实现?(尽可能让空间复杂度和时间复杂度降低)

例子:

push 1,push 2,  此时getMin()可以获得1. 继续push 0,此时getMin()获得0.  用pop()弹出栈顶元素后,此时getMin()获得1.

解题思路及优化

三种getMin()实现,第一种时间复杂度O(n)空间复杂度O(1)。第二种时间复杂度O(1)空间复杂度O(n)。第三种时间复杂度O(1)空间复杂度O(1),思考难度是递进的。

1. 时间复杂度O(n),空间复杂度O(1)的方法

适合编程初学者理解。思路:该数据结构内部有一个链表(数组也可以,但数组需要考虑当容量满了的扩容问题),push会将新元素连接到链表尾部,pop会将尾部元素删除。getMin()实现方式是遍历一遍该链表(数组),获取最小值并返回。

复杂度的计算:因为是遍历,所以时间复杂度O(n)。因为getMin()并没有申请新的辅助空间,所以空间复杂度O(1)。

C++代码如下:

#include<list>
#include<vector>
struct MinStack {
    std::vector<int> data;//C++中的动态数组,可以自动扩容。
    void push(int value) {
        data.push_back(value);//直接存入元素到链表结尾
    }
    int pop() {
        int returnValue = data.back();//先保存元素到一个变量
        data.pop_back();//删除链表结尾元素
        return returnValue;
    }
    int getMin() {
        int minValue=data.front();//设置minValue的值为第一个元素值
        for (int i = 0; i < data.size();i++) {//遍历数组,此处方法不限只要遍历即可
            minValue = std::min(data[i],minValue);//如果当前值比记录的minValue小,就把当前值赋给minValue
            //minValue = data[i] <minValue ? data[i] : minValue; 如果不想用min函数,也可以用三元表达式。功能等于上一行的代码
        }
        return minValue;
    }
    int top(){
        return data.back();
    }
};

  

1. 时间复杂度O(1),空间复杂度O(n)的方法

适合编程入门者理解。思路:该数据结构内部有两个栈(std::stack即可),使用设计模式的装饰器模式包装第一个栈的push和pop方法。此外每次push时,在第二个栈中放入此时容器中的最小值,每次pop时,在第二个栈中也弹出一个值。这样可以保证访问getMin()时,第二个栈最外的值就是最小数量。

如图:

 因为只需要比较新放入元素和之前栈顶的元素谁小,放入谁即可。(放入之前的栈顶元素就是放入之前的最小值)

复杂度的计算:需要一个额外的栈,存储的元素数量和第一个栈元素数量相同,所以空间复杂度为O(n)。因为每次获取直接获得栈的最外元素即可,所以时间复杂度为O(1)

C++代码如下

#include<stack>
#include<algorithm>//用到该头文件里边的min函数
struct MinStack {
    std::stack<int> data;
    std::stack<int> storeMin;
    void push(int value) {
        data.push(value);
        if (storeMin.empty() == true) {//如果之前没有元素,就无法拿出第一个元素比较
            storeMin.push(value);//此时直接放入即可
        }
        else {
            int minValue = std::min(storeMin.top(), value);//判断一下之前的最小值小还是新放入的值小
            storeMin.push(minValue);
        }
    }
    int pop() {
        int tem=top();
        data.pop();
        storeMin.pop();
        return tem;
    }
    int getMin() {
        return storeMin.top();
    }
    int top(){
        return data.top();
    }
};

  

3. 时间复杂度O(1),空间复杂度O(1)的实现

适合编程进阶的人来理解。思路:一共需要一个栈和一个变量。变量是当前的最小值。栈中存储的是和放入元素时和最小值的差值。这样虽然没直接存储每个元素,但其实可以随时计算出栈顶元素。pop时可以根据栈顶的差值和最小值计算出pop后应有的最小值。

复杂度的计算:只用到了一个额外的变量用于存储最小值,所以空间复杂度是O(1),并且每个操作都是根据栈顶元素和最小值进行计算就能得到,所以时间复杂度也是O(1)。

如图:

 

push的过程: 从头开始一步一步的讲:

1. 放入第一个元素3时,因为是第一个元素,可以特殊处理,最小值为3,该元素和最小值3的差(3-3=0)是0,所以栈存入0.

2.放入第二个元素4时,先考虑栈的存储:此时最小值为3,该元素减最小值(4-3)得1,所以栈存入1;最小值3和元素4比较,发现最小值不变,所以最小值仍为3

3.放入第三个元素2时,先考虑栈的存储:此时最小值为3,该元素减最小值(2-3)得-1,所以栈存入-1;最小值3和元素2比较,发现最小值应为新的最小值,所以最小值设置为2

4.放入第四个元素5时,先考虑栈的存储:此时最小值为2,该元素减最小值(5-2)得3,所以栈存入3;最小值2和元素5比较,发现最小值不变,所以最小值仍为2

4.放入第五个元素1时,先考虑栈的存储:此时最小值为2,该元素减最小值(1-2)得-1,所以栈存入-1;最小值2和元素1比较,发现最小值应为新的最小值,所以最小值设置为1

放入栈的元素和最小值的变换过程如上,下面阐述如何根据栈中存储的差值来计算出原来的元素。

top的过程(获得栈顶元素,因为我们存储的是差值所以需要一步转换)

情况1:如果栈中存储的是正数,说明放入的元素比最小值大,并且最小值肯定没有因为放入这个元素而改变。所以放入的元素就是 (最小值+差值)。比如图中第四个元素,

 3+2,可以得到这个元素是5。

情况2:如果栈中存储的是0,和正数情况同理,放入的元素就是(最小值+0)。

情况3:如果栈中存储的是负数,说明放入的元素比最小值小,导致因为放入这个值,最小值发生了改变。说明这个元素就是这个最小值。如图第三个元素放入时栈顶值为-1:

此时这个元素和最小值相同,值是2. 

pop的过程

pop时主要考虑,弹出了一个元素,最小值如何维护成正确的最小值。我们可以根据弹出元素和最小值来知道上一次最小值应该是多少。
情况1:弹出元素如果是正数,比如图中情况,我想弹出第四个,我怎么知道弹出最后最小值应该是多少。因为差值是正数,说明放入第四个元素时,第四个元素比当时的最小值大3,既然插入的值比最小值大,那么最小值肯定不会因为插入的这个值而改变,所以最小值仍为2.

情况2:弹出元素如果是0,和正数情况同理,说明新放入的元素正好和最小值相同,所以也不会改变最小值。

情况3:弹出元素如果是负数,如图,说明放入这个元素时,这个元素比当时的最小值小,从而改变了最小值。这个元素比当时的最小值小多少呢,因为放入的是差值所以小,所以公式为:(插入元素-当时的最小值=差值) => (当时的最小值=插入元素-差值)。只需要用这个元素的值减去差值即可,而该元素的值恰好就是后来的最小值,所以最终公式为:

当时的最小值=此时的最小值-差值 ,如下图2-(-1)=3

C++代码如下:

#include<stack>
#include<algorithm>//用到该头文件里边的min函数
struct MinStack {
	std::stack<long long> data;
	long long MinValue;
	void push(long long value) {
		if (data.empty() == true) {//如果栈为空说明放入的第一个元素,特殊处理,直接放入0
			data.push(0);
			MinValue = value;
		}
		else {
			data.push(value - MinValue);
			MinValue = std::min(value, MinValue);
		}
	}
	long long pop() {
		auto ReturnValue = top();//先获得此时的栈顶元素,一会当返回值用。这步调用的是成员函数top,和data.top有区别
		if (data.top() >= 0) {
			MinValue = MinValue;//其实就是不变,本行也可以不写,写了也会被编译器优化掉
		}
		else {//如果是负数的话
			MinValue -= data.top();//MinValue会发生改变,data.top()存储的是一个差值,和this->top()有区别
		}
		data.pop();
		return ReturnValue;
	}
	long long top() {//通过计算得到当前栈顶在push时传入的数值。
		if (data.top() >= 0) {
			return MinValue + data.top();
		}
		else {//如果最顶是负数,说明当前的最小值就是top的值
			return MinValue;
		}
	}
	long long getMin() {
		return MinValue;
	}
};

  

有问题和错误欢迎在评论区提出和指正,原创文章,转载请注明出处,谢谢大家的阅读~

posted on 2021-10-21 10:07  兰話一  阅读(175)  评论(0编辑  收藏  举报