请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
输入:
[“MaxQueue”,“pop_front”,“max_value”]
[[],[],[]]
输出: [null,-1,-1]
真费了老鼻子劲了,最烦写这种底层的,动不动堆内存overflow,数组越界或者逻辑错误把人整吐了。
双栈实现
一开口就是老数据结构了,双栈实现是一般问的比较多的,也是写起来最简单的8(因为直接可以调用STL里的stack:))。大概的原理就是一个栈outStack作为出队列的栈,一个栈inStack作为入队列的栈。当向队列中加入新元素时直接将该元素入栈inStack。当需要出栈时,直接从outStack出栈,如果outStack栈为空,则需要将inStack中的所有元素放入outStack中。对于maxvalue,只需在入栈和出栈时及时更新就好。
class MaxQueue {
public:
MaxQueue() {
this->maxNumber = INT_MIN;
}
bool empty(){
if (this->outStack.empty() && this->inStack.empty()){
return true;
}
return false;
}
int max_value() {
if (this->empty()){
this->maxNumber = -1;
}
return this->maxNumber;
}
void push_back(int value) {
this->maxNumber = max(this->maxNumber, value);
this->inStack.push(value);
}
int pop_front() {
if (this->empty()){
return -1;
}
if (this->outStack.empty()){
while (!this->inStack.empty()){
this->outStack.push(this->inStack.top());
this->inStack.pop();
}
}
int result = this->outStack.top();
this->outStack.pop();
if (this->maxNumber == result){
this->maxNumber = INT_MIN;
stack<int> temp;
while (!this->outStack.empty()){
this->maxNumber = max(this->maxNumber, this->outStack.top());
temp.push(this->outStack.top());
this->outStack.pop();
}
while (!temp.empty()){
this->outStack.push(temp.top());
temp.pop();
}
while (!this->inStack.empty()){
this->maxNumber = max(this->maxNumber, this->inStack.top());
temp.push(this->inStack.top());
this->inStack.pop();
}
while (!temp.empty()){
this->inStack.push(temp.top());
temp.pop();
}
}
return result;
}
private:
stack<int> outStack, inStack;
int maxNumber;
};
循环数组实现队列
循环数组是坑最多的,记得几年前学数据结构时写循环数组模拟队列就费了老大劲了。这次写的时候依然是错误百出,改了好久。。
循环数组模拟实质上是通过对数组索引求模来将数组练成一个环。一定要记得求模!!!!不然很容易就会造成堆内存的overflow,因为数组会越界。
其次,对于队列的判空和判慢也要注意。循环数组一定要空出来一个位置,即100size的数组只存99个元素。否则将会很难区分空队列和满队列。这里我采用的方法是,将head索引的位置空出来,tail索引指向下一刻元素即将放入的位置。这样当 head在tail的上一个位置时,队列为空,head=tail时,队列为满。另外,动态数组需要写好自动扩容。
class MaxQueue {
public:
MaxQueue() {
this->queueSize = 100;
this->numbers = new int[this->queueSize];
this->maxNumber = INT_MIN, this->head = 0, this->tail = 1;
}
bool empty() {
if ((this->head + 1) % this->queueSize == this->tail){
return true;
}
else{
return false;
}
}
bool full() {
if (this->head == this->tail){
return true;
}
else{
return false;
}
}
int max_value() {
if (this->empty()){
this->maxNumber = -1;
}
return this->maxNumber;
}
void push_back(int value) {
if (this->full()){
int* temp = new int[2 * this->queueSize];
int index = 1;
this->head = (this->head + 1) % this->queueSize;
while(this->head != this->tail){
temp[index++] = this->numbers[this->head];
this->head = (this->head + 1) % this->queueSize;
}
delete[] this->numbers;
this->numbers = temp;
this->head = 0, this->tail = index;
this->queueSize *= 2;
}
this->numbers[this->tail] = value;
this->tail = (this->tail + 1) % this->queueSize;
this->maxNumber = max(this->maxNumber, value);
}
int pop_front() {
if (this->empty()){
this->maxNumber = -1;
return -1;
}
this->head = (this->head + 1) % this->queueSize;
int result = this->numbers[this->head];
if (result == this->maxNumber){
this->maxNumber = INT_MIN;
int temp = (this->head + 1) % this->queueSize;
while (temp != this->tail){
this->maxNumber = max(this->maxNumber, this->numbers[temp]);
temp = (temp + 1) % this->queueSize;
}
}
return result;
}
private:
int* numbers;
int queueSize;
int head, tail;
int maxNumber;
};
单链表实现队列
这个应该是最简单的了,使用一个指针front表示头部,一个指针back表示尾部。front == back == NULL时表示队列为空,不会有队列为满的情况。主要维护好maxNumber的数值即可。
struct Node
{
int val;
Node* next;
Node(int a): val(a),next(NULL) {}
};
class MaxQueue {
public:
MaxQueue() {
this->front = NULL, this->back = NULL;
this->maxNumber = INT_MIN;
}
bool empty(){
if (this->front == NULL && this->back == NULL) return true;
else return false;
}
int max_value() {
if (this->empty()){
this->maxNumber = -1;
}
return this->maxNumber;
}
void push_back(int value) {
if (this->empty()){
this->back = new Node(value);
this->front = this->back;
}
else{
this->back->next = new Node(value);
this->back = this->back->next;
}
this->maxNumber = max(this->maxNumber, value);
}
int pop_front() {
if (this->empty()){
return -1;
}
int result = this->front->val;
if (this->front == this->back){
delete this->front;
this->front = this->back = NULL;
this->maxNumber = INT_MIN;
}
else{
Node* temp = this->front;
this->front = this->front->next;
delete temp;
temp = this->front;
if (this->maxNumber == result){
this->maxNumber = INT_MIN;
while (temp != NULL){
this->maxNumber = max(this->maxNumber, temp->val);
temp = temp->next;
}
}
}
return result;
}
private:
Node* front;
Node* back;
int maxNumber;
};
关于所有操作O(1)的平均时间复杂的
其实开始时,没有特别关注O(1)平均时间复杂度这个要求。其实开始觉得不太现实,因为就算你维护一个最大堆那时间复杂度也要O(logn)了。只能说平均时间复杂度的要求不太严格,在少部分时候承受一些时间复杂度也是可以接受的。LeetCode的官方题解给出了一个方法:维护一个递减的双端队列,其实这个思路挺巧妙的,应该比遍历要节省时间,mark一下。大意就是说,“当一个元素进入队列的时候,它前面所有比它小的元素就不会再对答案产生影响。”通过维护一个递减的双端队列,该队列的头元素就是当前队列中最大的元素,并且,只有当队列中小于这个元素的元素全部被取出时,它才会被取出,所以说,只要辅助的双端队列中的元素被取完了,那原队列也已经成为了空队列。这里有个动画演示的非常清楚:动画。
class MaxQueue {
queue<int> q;
deque<int> d;
public:
MaxQueue() {
}
int max_value() {
if (d.empty())
return -1;
return d.front();
}
void push_back(int value) {
while (!d.empty() && d.back() < value) {
d.pop_back();
}
d.push_back(value);
q.push(value);
}
int pop_front() {
if (q.empty())
return -1;
int ans = q.front();
if (ans == d.front()) {
d.pop_front();
}
q.pop();
return ans;
}
};