前言
欢迎大家积极在评论区留言发表自己的看法,知无不言,言无不尽,养成每天刷题的习惯,也可以自己发布优质的解题报告,供社区一同鉴赏,吸引一波自己的核心粉丝。
今天是五月集训第十六天:队列🔥🔥
一、练习题目
二、算法思路
- 1、933. 最近的请求次数:队列简单模拟。
- 2、2073. 买票需要的时间:设计一个结构体用来记录买票人在数组中的位置和需要买票的总数,用这个结构体定义一个数组模拟队列。
- 3、641. 设计循环双端队列:其实不需要用双端队列用数组来模拟就可以,或者链表,链表便于插入和删除,数组利于查找,各有好处,那我选择数组吧😄。
- 4、1670.设计前中后队列:上一题的应用题,做起来非常爽~✈️
三、源码剖析
// 933. 最近的请求次数
class RecentCounter {
queue<int> q;
public:
RecentCounter() {
}
int ping(int t) {
q.push(t);
while(q.front() < t - 3000) {
q.pop(); //(1)
}
return q.size();
}
};
- 1、队首大于3000弹出队列。
// 2073. 买票需要的时间
class Solution {
struct People
{
int pos, ticket;
}; //(1)
int front, rear;
People data[20000]; //(2)
public:
int timeRequiredToBuy(vector<int>& tickets, int k) {
front = rear = 0;
int time = 0;
for(int i = 0; i < tickets.size(); ++i) {
People a;
a.pos = i;
a.ticket = tickets[i];
data[rear++] = a;
} //(3)
while (front < rear) //(4)
{
People p = data[front++];
time++;
--p.ticket;
if(p.ticket == 0) {
if(p.pos == k) {
return time;
}
} else {
data[rear++] = p;
}
}
return -1;
}
};
- 1、设计一个结构体;
- 2、定义一个结构体数组来模拟队列;
- 3、将题目中的tickets转化到结构数组队列中;
- 4、当队列不为空时候继续遍历,进行循环一次相当于模拟购票一次,时间加一,如果当前这个人票买完了,同时是我们要找的那个人,返回购票的总时间否则放到队尾继续模拟。
// 641. 设计循环双端队列
class MyCircularDeque {
#define CENTER 2000
int data[4000]; //(1)
int front, rear;
int cap;
public:
MyCircularDeque(int k) {
cap = k;
front = CENTER;
rear = front - 1; //(2)
}
bool insertFront(int value) {
if(isFull()) {
return false;
} else {
data[--front] = value;
}
return true;
}
bool insertLast(int value) {
if(isFull()) {
return false;
} else {
data[++rear] = value;
}
return true;
}
bool deleteFront() {
if(isEmpty()) {
return false;
} else {
++front;
}
return true;
}
bool deleteLast() {
if(isEmpty()) {
return false;
} else {
--rear;
}
return true;
}
int getFront() {
if(isEmpty()) {
return -1;
}
return data[front];
}
int getRear() {
if(isEmpty()) {
return -1;
}
return data[rear];
}
bool isEmpty() {
return (rear - front + 1) == 0 ? 1 : 0;
}
bool isFull() {
return (rear - front + 1) == cap ? 1 : 0;
}
};
- 1、因为执行的次数不会超过2000,所以设置一个长度为4000的数组,并且将队首队尾初始化为数组的中间,这样做的话防止要去考虑数组下标越界的问题;
- 2、初始化front = 2000, rear = front - 1,这样做的好处是 rear - front + 1可以判断队列的长度,是不是空或者满。
// 1670. 设计前中后队列
class FrontMiddleBackQueue {
#define CENTER 2000
int data[4000];
int rear, front; //(1)
public:
int getCount() {
return rear - front + 1;
}
FrontMiddleBackQueue() {
front = CENTER;
rear = CENTER - 1;
}
void pushFront(int val) {
data[--front] = val;
}
// X front - 1
// XX front + 1
// xxx front + 1
// xxxx front + 2
// xxxxx front + 3
void pushMiddle(int val) {
if(getCount() == 1) {
pushFront(val);
return;
}
int x = getCount();
int y = front + x / 2;
for(int i = rear + 1; i > y; --i) {
data[i] = data[i - 1];
}
data[y] = val;
++rear;
}
void pushBack(int val) {
data[++rear] = val;
}
int popFront() {
if(getCount() != 0) {
return data[front++];
}
return -1;
}
// x front
// xx front
// xxx front + 1
// xxxx front + 1
// xxxxx front + 2
int popMiddle() {
if(getCount() != 0) {
int x = getCount();
int y = front + (x-1) / 2;
int tmp = data[y];
for(int i = y; i < rear; ++i) {
data[i] = data[i + 1];
}
rear--;
return tmp;
}
return -1;
}
int popBack() {
if(getCount() != 0) {
return data[rear--];
}
return -1;
}
};
- 1、思想和上一题一样我就不赘述;
- 2、在中间插入的时候需要注意的,插入的位置通过数学归纳法得出,大佬的思想😄
1. 插入中间的操作:如果是只用一个就插最前面正常操作,所以这个单独判断,队列长度大于1的 插 入 位 置 = f r o n t + 队 列 长 度 / 2 插入位置 = front + 队列长度 / 2 插入位置=front+队列长度/2;
2. 删除操作,归纳出来是 插 入 位 置 = f r o n t + ( 队 列 长 度 − 1 ) / 2 插入位置 = front + (队列长度 - 1) / 2 插入位置=front+(队列长度−1)/2。