LeetCode系列之队列专题

1. 队列题目概述

https://leetcode-cn.com/tag/queue/

队列是一种先进先出(FIFO)的线性表。

2. 典型题目

2.1 设计循环队列

https://leetcode-cn.com/problems/design-circular-queue/

class MyCircularQueue {
public:
    /** Initialize your data structure here. Set the size of the queue to be k. */
    MyCircularQueue(int k) : head(0), count(0), capacity(k) {
        data = std::vector<int>(k);
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    bool enQueue(int value) {
        if (isFull()) return false;
        int newTail = circleIncrement(tail());
        data[newTail] = value;
        count++;
        return true;
    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    bool deQueue() {
        if (isEmpty()) return false;
        head = circleIncrement(head);
        count--;
        return true;
    }
    
    /** Get the front item from the queue. */
    int Front() {
        if (isEmpty()) return -1;
        return data[head];
    }
    
    /** Get the last item from the queue. */
    int Rear() {
        if (isEmpty()) return -1;
        return data[tail()];
    }
    
    /** Checks whether the circular queue is empty or not. */
    bool isEmpty() {
        return count == 0;
    }
    
    /** Checks whether the circular queue is full or not. */
    bool isFull() {
        return count == capacity;
    }

private:
    int tail() {return (head + count - 1) % capacity;}
    int circleIncrement(int index) {return (index + 1) % capacity;}
    std::vector<int> data;
    int head;
    int count;
    int capacity;
};

该解法非线程安全,如果改进,需要加锁。

题解中有一段话说得很好:

设计数据结构的关键是如何设计“属性”,好的设计属性数量更少。

  • 属性数量少说明属性之间冗余更低。
  • 属性冗余度越低,操作逻辑越简单,发生错误的可能性越低。
  • 属性数量少,使用的空间也少,操作性能更高。

但是,也不建议使用最少的属性数量。一定的冗余可以降低操作的时间复杂度,达到时间复杂度和空间复杂度的相对平衡。

时间复杂度O(1),空间复杂度O(N)。

2.2 设计循环双端队列

https://leetcode-cn.com/problems/design-circular-deque/

class MyCircularDeque {
public:
    /** Initialize your data structure here. Set the size of the deque to be k. */
    MyCircularDeque(int k) : data(k), head(0), capacity(k), count(0) {

    }
    
    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    bool insertFront(int value) {
        if (isFull()) return false;
        head = decrementIndex(head);
        data[head] = value;
        count++;
        return true;
    }
    
    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    bool insertLast(int value) {
        if (isFull()) return false;
        int tail = (head + count) % capacity;
        data[tail] = value;
        count++;
        return true;
    }
    
    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    bool deleteFront() {
        if (isEmpty()) return false;
        head = incrementIndex(head);
        count--;
        return true;
    }
    
    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    bool deleteLast() {
        if (isEmpty()) return false;
        count--;
        return true;
    }
    
    /** Get the front item from the deque. */
    int getFront() {
        if (isEmpty()) return -1;
        return data[head];
    }
    
    /** Get the last item from the deque. */
    int getRear() {
        if (isEmpty()) return -1;
        int index = (head + count - 1 + capacity) % capacity;
        return data[index];
    }
    
    /** Checks whether the circular deque is empty or not. */
    bool isEmpty() {
        return count == 0;
    }
    
    /** Checks whether the circular deque is full or not. */
    bool isFull() {
        return count == capacity;
    }

private:
    int incrementIndex(int index) {
        return (index + 1) % capacity;
    }
    int decrementIndex(int index) {
        return (index + capacity - 1) % capacity;
    }

    std::vector<int> data;
    int head;
    int capacity;
    int count;
};

时间复杂度O(1),空间复杂度O(N)。

2.3 最近的请求次数

https://leetcode-cn.com/problems/number-of-recent-calls/

class RecentCounter {
public:
    RecentCounter() {

    }
    
    int ping(int t) {
        data.push(t);
        while (data.front() < t - 3000) {
            data.pop();
        }
        return data.size();
    }

private:
    std::queue<int> data;
};

时间复杂度O(Q),Q是ping的次数。

空间复杂度O(W),W=3000,是队列中最多存储的记录数。

对于时间复杂度,data.pop()的次数小于3000次,我怎么觉得应该是O(1)呢?

2.4 和至少为K的最短子数组

https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/

2.5 第k个数

https://leetcode-cn.com/problems/get-kth-magic-number-lcci/

int getKthMagicNumber(int k) {
    std::vector<int> dp(k + 1);
    dp[0] = 1;
    int p3 = 0, p5 = 0, p7 = 0;
    for (int i = 1; i < k; i++) {
        dp[i] = min(dp[p3] * 3, dp[p5] * 5, dp[p7] * 7);
        if (dp[i] == dp[p3] * 3) p3++;
        if (dp[i] == dp[p5] * 5) p5++;
        if (dp[i] == dp[p7] * 7) p7++;
    }
    return dp[k - 1];
}

int min(int a, int b, int c) {
    return std::min(a, std::min(b, c));
}

这一段的分析很好

如果便于理解,可以考虑成二维数组

dp[i]:1, 3, 5, 7, ...

dp3[i]:3, 9, 15, 21, ...

dp5[i]:5, 15, 25, 35, ...

dp7[i]:7, 21, 35, 49, ...

计算dp[i]可以相应更新下面的元素(dp3[i], dp5[i], dp7[i]),取下面元素的最小值又生成了下一个dp[i+1]。以此类推。

由此,有以下推论:dp3[i] == dp[i]*3; dp5[i] == dp[i]*5; dp7[i] == dp[i]*7;

所以,dp[i] = min(dp3[index1], dp5[index2], dp7[index3]) 就可以化简为 dp[i] = min(dp[index1]*3, dp[index2]*5, dp[index]*7)的形式。从而节省了空间(使用一维数组就可以了)。

时间复杂度O(N),空间复杂度O(N)。

2.6 矩形区域不超过K的最大数值和

https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/

2.7 滑动窗口的最大值

https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

重复题目,参见《LeetCode系列之堆专题

2.8 任务调度器

https://leetcode-cn.com/problems/task-scheduler/

3. 总结

posted @ 2020-08-19 17:43  不写诗的诗人小安  阅读(200)  评论(0编辑  收藏  举报