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/