每日一题:队列的循环数组实现、链表实现和双栈实现

请定义一个队列并实现函数 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;
    }
};
发布了76 篇原创文章 · 获赞 10 · 访问量 8275

猜你喜欢

转载自blog.csdn.net/weixin_38742280/article/details/104732262