一、基础知识
1. 队列
队列的定义:
队列是在一端进行插入,再另一端进行删除的线性表。特点是先进先出(First In First Out),简称FIFO
获取队首数据:
对于一个队列来说,只能获取队首数据,一般不支持获取其他数据。
获取队列元素个数:
队列元素个数一般用一个额外变量存储,入队时加一,出队时减一。时间复杂度为O(1)
队列的判空:
当队列元素个数为零时,就是一个空队,空队不允许出队操作。
2. 双端队列
双端队列的定义:
双端队列是一种具有队列和栈的性质的数据结构,也就是deque(double-ended queue),是一种限定插入和删除操作在表的两端进行的线性表。这两端分别被称为队首和队尾.
模拟栈:
双端队列可以用来在一端进行插入和删除,从而实现栈的功能。比如在队首进行插入和删除操作,从而模拟栈的入栈和出栈的过程。
模拟队列:
双端队列也可以用于限定只在一端插入,另一端删除,从而实现队列的功能。比如在队尾进行插入,队首进行删除,从而模拟FIFO队列的入队和出队的过程。
清空队列:
队列的清空操作,就是一直出队,直到队列为空的过程,当队首和队尾正好错开一个位置时,就代表队尾为空了。
二、相关例题
- 设计一个队列,队列中的元素保持单调递增
- 每次调用ping操作时,将队首元素加上3000,如果比输入的t值小,则从队首弹出。
class RecentCounter {
int front, rear;
int data[10010];
public:
RecentCounter() {
front = 0;
rear = -1;
}
int ping(int t) {
data[++rear] = t;
while(rear - front +1 > 0 && data[front] + 3000 < t) {
front++;
}
return rear - front + 1;
}
};
2.买票需要的时间
- 定义一个结构体,存储位置和需要票的个数
- 遍历一遍数组,将位置和需要票的个数在数组对象中进行存储
- 再遍历一遍数组,把需要票的个数减1,时间就加1,模拟买票的过程。
- 当需要的票的个数为0,也就是买到足够的票了之后,判断是不是在题目要求的位置。如果在对应的位置就返回需要的时间
- 如果不是在对应的位置,就把这个数组对象在队尾进行入队操作,并且在队首进行出队操作,把旧的数组对象删掉。
- 找不到就返回-1
class Solution {
struct People {
int pos;
int val;
};
int front, rear;
People data[20000];
public:
int timeRequiredToBuy(vector<int>& tickets, int k) {
//遍历一遍
int i;
int time = 0;
front = rear = 0;
for(i = 0; i < tickets.size(); ++i) {
People a;
a.pos = i;
a.val = tickets[i];
data[rear++] = a;
}
while(front < rear) {
//遍历一遍,做判断
//头插法
People p = data[front++];//指向队列中新的元素,并删除旧的元素
time++;
--p.val;
if(p.val == 0) {
if(p.pos == k) {
return time;
}
} else {
//p不为0时,添加到队列的末端
data[rear++] = p;
}
}
//找不到,返回-1
return -1;
}
};
- 这道题不用设计双端队列
- 因为执行次数不会超过2000次,所以可以初始化一个元素个数为4000的数组,并把队首和队尾初始化在数组中间。
- 比如front=2000, rear = front-1;而rear-front+1就是队列中的元素个数
- 要注意什么时候时++front,还是front++;反之,rear也一样。
class MyCircularDeque {
#define CENTER 2500
int data[5000];
int cap;
int front, rear;
public:
MyCircularDeque(int k) {
cap = k;
front = CENTER;
rear = front - 1;
}
int getCount() {
return rear - front + 1;
}
bool insertFront(int value) {
if(isFull()) return false;
data[--front] = value;
return true;
}
bool insertLast(int value) {
if(isFull()) return false;
data[++rear] = value;
return true;
}
bool deleteFront() {
if(isEmpty()) return false;
front++;
return true;
}
bool deleteLast() {
if(isEmpty()) return false;
rear--;
return true;
}
int getFront() {
if(isEmpty()) return -1;
return data[front];
}
int getRear() {
if(isEmpty()) return -1;
return data[rear];
}
bool isEmpty() {
return getCount() == 0;
}
bool isFull() {
return getCount() == cap;
}
};
/**
* Your MyCircularDeque object will be instantiated and called as such:
* MyCircularDeque* obj = new MyCircularDeque(k);
* bool param_1 = obj->insertFront(value);
* bool param_2 = obj->insertLast(value);
* bool param_3 = obj->deleteFront();
* bool param_4 = obj->deleteLast();
* int param_5 = obj->getFront();
* int param_6 = obj->getRear();
* bool param_7 = obj->isEmpty();
* bool param_8 = obj->isFull();
*/
- 跟上一道题目类似,数据量小,所以可以用数组
- 在中间插入和中间删除的时候,可以用数学归纳法进行分析
class FrontMiddleBackQueue {
#define CENTER 2500
int data[5000];
int front, rear;
public:
FrontMiddleBackQueue() {
front = CENTER;
rear = front - 1;
}
void pushFront(int val) {
data[--front] = val;
}
void pushMiddle(int val) {
int x = rear - front + 1;//元素个数
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(rear - front + 1 == 0) return -1;
return data[front++];
}
int popMiddle() {
int x = rear - front + 1;
if(x == 0) return -1;
int y = front+(x-1) / 2;
int temp = data[y];
for(int i = y; i < rear; ++i) {
data[i] = data[i+1];
}
rear--;
return temp;
}
int popBack() {
if(rear - front + 1 == 0) return -1;
return data[rear--];
}
};
/**
****** ***** 2
***** **** 2
**** *** 1
*** ** 1
** * 0
* 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();
*/
浙公网安备 33010602011771号