請定義一個隊列並實現函數 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;
}
};