如何找到一个栈中的最小值
又是在公众号上看到了一个题目,再一次被虐.感觉自己已经快要习惯了。
题目:实现一个栈,带有出栈(pop),入栈(push),取最小元素(getMin)三个方法。要保证这三个方法的时间复杂度都是O(1)。
当我看到这道题时,脑子里的第一个想法就是:在结构体或对象中设置个指针就好了,让指针永远指向最小值。用c++代码实现就是:
//Stack.h
#ifndef _Stack_H
#define _Stack_H
#include <iostream>
using namespace std;
#define N 10
class Stack{
private:
int data[N]; //数据
int top; //栈顶
int min; //最小值指针
public:
Stack();
~Stack();
void push(int data); //入栈
int pop(void); //出栈
int getMin(void); //取数据最小值
};
#endif
乍一看貌似没什么问题,只要在构造函数中初始化一下min=0,然后每次入栈时都要将入栈数据data[top]与data[min]做比较,如果data[top]小于data[min]则必须执行min=top,这样min永远指向栈中最小数据。
但是这样的设计是有问题的。想想是什么问题?
出栈!若当前栈中最小值出栈了,min该指向哪里?没错,它该指向之前栈中的次小值。那么它又要怎么指向次小值?麻烦就来了,无法用O(1)的时间复杂度解决了。
可以看出,这种方法的漏洞在于,当最小值出栈时,无法找到次小值来顶替此时最小值的位置。也就是说只要我们解决这个问题,就可以保证无论怎么入栈出栈,都可以随时返回当前栈中最小值。
如何解决呢?用什么方法可以保证在时间复杂度为O(1)的情况下一次找到次小值?存储!将每一次得到的最小值的下标存储在一个数据结构中,这样就可以在当前最小值出栈时直接找到次小值来顶替当前最小值。至于这个数据结构,我建议是栈。
我们假设之前的栈为Stack_A,这个存储最小值下标的栈叫Stack_B。因为每一次入栈Stack_B的都是Stack_A当前的最小值下标,也就是说,Stack_B的后入栈元素永远比先入栈元素对应的Stack_A数据要小。如果此时Stack_B栈顶元素出栈,Stack_B的top减1,此时的Stack_B栈顶元素为上一次入栈Stack_B时的元素,即当前Stack_A最小值的下标。栈的“First In Last Out”特点,让它成为最适用于当前环境的数据结构。
理论分析完了,我们看代码:
//Stack.h头文件
#ifndef _Stack_H
#define _Stack_H
#include <iostream>
using namespace std;
#define N 10
class Stack_B;
class Stack_A{
private:
int data[N]; //A数据
int top; //A栈顶
Stack_B *sta; //B栈指针
public:
Stack_A();
~Stack_A();
void push(int data); //入栈
int pop(void); //出栈
int getMin(void); //取数据最小值
};
class Stack_B{
private:
int data[N]; //B数据
int top; //B栈顶
public:
Stack_B();
void push(int index); //入栈
int pop(void); //出栈
int getTop(); //取栈顶
};
#endif
//Stack.cpp
#include "Stack.h"
/*************** Stack_A **************************/
Stack_A::Stack_A(){
top=0; //将栈A栈顶指针置0
sta=new Stack_B(); //创建栈B对象,用来存储栈A每一次入栈的最小值
}
Stack_A::~Stack_A(){
delete sta; //释放sta动态申请空间
}
void Stack_A::push(int data){
if(top==0||data<=getMin()) //当第一次入栈或入栈数据小于当前最小值时,
sta->push(top); //将当前栈顶指针入Stack_B栈
this->data[top++]=data; //将数据入Stack_A栈
}
int Stack_A::pop(){
if(sta->getTop()==--top) //若出栈时Stack_B栈顶元素为Stack_A的top-1值,即Stack_A出栈时最小为当前元素时,
sta->pop(); //Stack_B也出栈
return data[top]; //返回栈顶元素
}
int Stack_A::getMin(){
return data[sta->getTop()]; //返回Stack_A最小元素
}
/********************************************************/
/**************** Stack_B ***************************/
Stack_B::Stack_B(){
top=0; //将栈B栈顶指针置0
}
void Stack_B::push(int index){
this->data[top++]=index; //将栈A的当前最小值下标入栈B
}
int Stack_B::pop(){
return data[--top]; //栈B栈顶元素出栈
}
int Stack_B::getTop(){
return data[top-1]; //返回栈B栈顶元素,即返回当前Stack_A最小值下标
}
/*******************************************************/
//client客户端测试数据
#include "Stack.h"
int main(int argc, char *argv[])
{
Stack_A* p=new Stack_A(); //动态创建Stack_A对象
p->push(5); //入栈
p->push(12);
p->push(3);
cout<<"getMin:"<<p->getMin()<<endl; //显示最小值
cout<<"pop:"<<p->pop()<<endl; //出栈
cout<<"pop:"<<p->pop()<<endl;
cout<<"getMin:"<<p->getMin()<<endl;
delete p;
return 0;
}
- 实验结果
当然了,如果对这个过程理解的不是很深刻的话,就会像我一样,被人家说一句“如果入栈元素大于min小于次小值呢”就开始陷入沉思一脸懵逼怀疑自己怀疑人生。
为了证明我真的参悟了这个算法,我要解释一下:如果入栈元素大于min的话,不管它小于谁,出栈的时候它一定比min先出栈;而想要查看正确的次小值,就一定要min先出栈。脑子不要被人一问就混乱了,而忘记这个出栈顺序。