文章目录
- 0. Leetcode [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/)
- 1. Leetcode [2073. 买票需要的时间](https://leetcode.cn/problems/time-needed-to-buy-tickets/)
- 2. Leetcode [641. 设计循环双端队列](https://leetcode.cn/problems/design-circular-deque/)
- 3. Leetcode [1670. 设计前中后队列](https://leetcode.cn/problems/design-front-middle-back-queue/)
- 总结
0. Leetcode 933. 最近的请求次数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
分析与解答
使用队列,放入新的数时对队首元素进行判断,若不满足条件则弹出,将队列大小计入结果中:
class RecentCounter {
deque<int> deq;
public:
int ping(int t) {
deq.push_back(t);
while (deq.front() < t - 3000) {
deq.pop_front();
}
return deq.size();
}
};
/**
* Your RecentCounter object will be instantiated and called as such:
* RecentCounter* obj = new RecentCounter();
* int param_1 = obj->ping(t);
*/
1. Leetcode 2073. 买票需要的时间
有 n 个人前来排队买票,其中第 0 人站在队伍 最前方 ,第 (n - 1) 人站在队伍 最后方 。
给你一个下标从 0 开始的整数数组 tickets ,数组长度为 n ,其中第 i 人想要购买的票数为 tickets[i] 。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。
分析与解答
可以使用模拟法,用队列模拟买票过程,买票时从队首弹出,计数 -1,若计数大于 0,向队尾插入即可。注意这里需要记录每个人在原来队列的位置,因此队列中的元素为 <票数,位置> 数对:
class Solution {
public:
int timeRequiredToBuy(vector<int>& tickets, int k) {
int result(0);
deque<std::pair<int, int>> deq;
for (int i = 0; i < tickets.size(); ++i) {
deq.push_back(make_pair(tickets[i], i));
}
while (get<1>(deq.front()) != k ||
get<1>(deq.front()) == k && get<0>(deq.front()) != 1) {
get<0>(deq.front())--;
auto ele = deq.front();
if (get<0>(deq.front()) != 0) {
deq.pop_front();
deq.push_back(ele);
} else {
deq.pop_front();
}
result++;
}
return result + 1;
}
};
2. Leetcode 641. 设计循环双端队列
设计实现双端队列。
实现 MyCircularDeque 类:
MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。
boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false 。
boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。
分析与解答
设计题,按照题目要求进行设计即可。除了使用循环数组,也可以用提前分配好空间的数组进行模拟:
class MyCircularDeque {
int* data; // 存储数据
int head; // 当前第一个元素下标
int tail; // 当前最后一个元素下标
int maxDataSize; // 最大数据数量
public:
MyCircularDeque(int k) {
data = new int[k];
head = -1;
tail = -1;
maxDataSize = k;
}
bool insertFront(int value) {
if (head < 0) {
data[0] = value;
head = 0;
tail = 0;
return true;
} else {
int curHead(head);
head--;
if (head < 0) {
head = maxDataSize + head;
}
if (head != tail) {
data[head] = value;
return true;
} else {
head = curHead;
std::cout << head << std::endl;
return false;
}
}
}
bool insertLast(int value) {
if (tail < 0) {
data[0] = value;
head = 0;
tail = 0;
return true;
} else {
int curTail(tail);
tail++;
if (tail >= maxDataSize) {
tail = tail % maxDataSize;
}
if (head != tail) {
data[tail] = value;
return true;
} else {
tail = curTail;
return false;
}
}
}
bool deleteFront() {
if (head < 0) {
return false;
}
if (head == tail) { // 只有一个元素
head = -1;
tail = -1;
return true;
}
head++;
if (head >= maxDataSize) {
head = head % maxDataSize;
}
return true;
}
bool deleteLast() {
if (tail < 0) {
return false;
}
if (head == tail) { // 只有一个元素
head = -1;
tail = -1;
return true;
}
tail--;
if (tail < 0) {
tail = maxDataSize + tail;
}
return true;
}
int getFront() {
if (head < 0) {
return -1;
}
return data[head];
}
int getRear() {
if (tail < 0) {
return -1;
}
return data[tail];
}
bool isEmpty() {
return head < 0? true: false;
}
bool isFull() {
if (head < 0) {
return false;
}
int headPos(head);
head--;
if (head < 0) {
head = maxDataSize + head;
}
if (head == tail) {
head = headPos;
return true;
} else {
head = headPos;
return false;
}
}
};
/**
* Your MyCircularDeque object will be instantiated and called as such:
* MyCircularDeque obj = new MyCircularDeque(k);
* boolean param_1 = obj.insertFront(value);
* boolean param_2 = obj.insertLast(value);
* boolean param_3 = obj.deleteFront();
* boolean param_4 = obj.deleteLast();
* int param_5 = obj.getFront();
* int param_6 = obj.getRear();
* boolean param_7 = obj.isEmpty();
* boolean param_8 = obj.isFull();
*/
3. Leetcode 1670. 设计前中后队列
请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。
请你完成 FrontMiddleBack 类:
FrontMiddleBack() 初始化队列。
void pushFront(int val) 将 val 添加到队列的 最前面 。
void pushMiddle(int val) 将 val 添加到队列的 正中间 。
void pushBack(int val) 将 val 添加到队里的 最后面 。
int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:
将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。
从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素,返回 3 ,数组变为 [1, 2, 4, 5, 6] 。
分析与解答
设计时可以用两个队列模拟,让中间数始终保持在第一个队列的最后一个位置。这里我们直接用数组进行模拟:
class FrontMiddleBackQueue {
int data[2010]; // 存储数据
int head; // 头插入时使用的位置
int tail; // 尾插入时使用的位置
int dataSize; // 数据大小
public:
FrontMiddleBackQueue() {
head = 1004;
tail = 1005;
dataSize = 0;
}
void pushFront(int val) {
data[head] = val;
head--;
dataSize++;
}
void pushMiddle(int val) {
int insertPos(0);
if (dataSize & 0x1 == 1) { // 根据数据大小与头元素位置计算中间元素位置
// 奇数
insertPos = head + dataSize / 2 + 1;
} else {
insertPos = head + dataSize / 2 + 1;
}
for (int i = tail; i >= insertPos + 1; --i) { // 中间位置往后的数据向后移动
data[i] = data[i - 1];
}
data[insertPos] = val;
dataSize++;
tail++;
}
void pushBack(int val) {
data[tail] = val;
tail++;
dataSize++;
}
int popFront() {
if (dataSize > 0) {
int result(data[head + 1]);
head++;
dataSize--;
return result;
}
return -1;
}
int popMiddle() {
if (dataSize == 0) {
return -1;
}
int middlePos(0);
if (dataSize & 0x1 == 1) {
// 奇数
middlePos = head + dataSize / 2 + 1;
} else {
middlePos = head + dataSize / 2;
}
int result(data[middlePos]);
for (int i = middlePos; i < tail; ++i) { // 将中间位置往后的数据向前移动
data[i] = data[i + 1];
}
tail--;
dataSize--;
return result;
}
int popBack() {
if (dataSize > 0) {
int result(data[tail - 1]);
tail--;
dataSize--;
return result;
}
return -1;
}
};
/**
* Your FrontMiddleBackQueue object will be instantiated and called as such:
* FrontMiddleBackQueue* obj = new FrontMiddleBackQueue();
* obj->pushFront(val);
* obj->pushMiddle(val);
* obj->pushBack(val);
* int param_4 = obj->popFront();
* int param_5 = obj->popMiddle();
* int param_6 = obj->popBack();
*/
总结
队列数据结构并不复杂,要点在于弄清下标含义以及其变化情况。