每日一題:隊列的循環數組實現、鏈表實現和雙棧實現

請定義一個隊列並實現函數 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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章