一、基础
1.套路
1.队列常用在 BFS 中,见 网格图题单 和 图论题单。
2.队列(queue
)是容器适配器,功能较少。
队尾插入元素,队首弹出元素,可以访问队首元素、队尾元素和队列长度。
无begin()
,end()
等迭代器
queue<int> que;
que.push(x); // 队尾插入元素
que.pop(); // 队首弹出元素
int x=que.front(); // 访问队首元素
int x=que.back(); // 访问队尾元素
int len=que.size(); // 访问队列长度
bool tag=que.empty(); // 判断队列是否为空
3.双端队列(deque
,double-ended
)是容器,功能较多。
队首和队尾都能插入元素和弹出元素,可以访问队首元素、队尾元素和队列长度。
有begin()
,end()
,rbegin()
,rend()
等迭代器
deque<int> deq;
deq.push_front(x); // 队首插入元素
deq.pop_front(); // 队首弹出元素
deq.push_back(x); // 队尾插入元素
deq.pop_back(); // 队尾弹出元素
int x=deq.front(); // 访问队首元素
int x=deq.back(); // 访问队尾元素
int len=deq.size(); // 访问队列长度
bool tag=deq.empty(); // 判断队列是否为空
2.题目描述
3.学习经验
1.队列用于先入先出结构,每次要处理最早进来的元素
2.双端队列适合队首和队尾都要插入和删除操作
1. 933. 最近的请求次数(简单,学习)
933. 最近的请求次数 - 力扣(LeetCode)
思想
1.写一个 RecentCounter
类来计算特定时间范围内最近的请求。
请你实现 RecentCounter
类:
RecentCounter()
初始化计数器,请求数为 0 。int ping(int t)
在时间t
添加一个新请求,其中t
表示以毫秒为单位的某个时间,并返回过去3000
毫秒内发生的所有请求数(包括新请求)。确切地说,返回在[t-3000, t]
内发生的请求数。
保证 每次对ping
的调用都使用比之前更大的t
值。
2.因为请求t
是单调递增输入的,而每次查询为[t-3000,t]
,所以t-3000
之前的请求就没有用了,所以是先入先出,典型的队列,不符合要求的请求弹出队列,答案就是队列长度
代码
2. 950. 按递增顺序显示卡牌(中等,学习)
950. 按递增顺序显示卡牌 - 力扣(LeetCode)
思想
1.牌组中的每张卡牌都对应有一个唯一的整数。你可以按你想要的顺序对这套卡片进行排序。
最初,这些卡牌在牌组里是正面朝下的(即,未显示状态)。
现在,重复执行以下步骤,直到显示所有卡牌为止:
- 从牌组顶部抽一张牌,显示它,然后将其从牌组中移出。
- 如果牌组中仍有牌,则将下一张处于牌组顶部的牌放在牌组的底部。
- 如果仍有未显示的牌,那么返回步骤 1。否则,停止行动。
返回能以递增顺序显示卡牌的牌组顺序。
答案中的第一张牌被认为处于牌堆顶部。
2.举个例子:
给的数组[17,13,11,2,3,5,7]
(不重要)
[2,13,3,11,5,17,7]
->[2,3,5,7,11,13,17]
(递增顺序)
现在让我们求数组[2,13,3,11,5,17,7]
,已知[2,3,5,7,11,13,17]
,所以是逆序求。原来的顺序是从小元素开始,先取最前面的,然后最前面的放到最后面。所以逆序就是从大元素开始,先把最后面的放到最前面,然后在最前面插入元素。这种最前面和最后面都要插入和删除的拿双端队列实现。
代码
class Solution {
public:vector<int> deckRevealedIncreasing(vector<int>& deck) {int n=deck.size();deque<int> deq;sort(deck.begin(),deck.end());// 逆序,从大到小for(int i=n-1;i>=0;--i){// 先队尾元素到队首if(!deq.empty()){deq.push_front(deq.back());deq.pop_back();}// 再队首插入元素deq.push_front(deck[i]);}vector<int> res(deq.begin(),deq.end());return res;}
};
3. 649. Dota2 参议院(中等,学习)
649. Dota2 参议院 - 力扣(LeetCode)
思想
1.Dota2 的世界里有两个阵营:Radiant
(天辉)和 Dire
(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的 一 项:
- 禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失 所有的权利 。
- 宣布胜利:如果参议员发现有权利投票的参议员都是 同一个阵营的 ,他可以宣布胜利并决定在游戏中的有关变化。
给你一个字符串senate
代表每个参议员的阵营。字母'R'
和'D'
分别代表了Radiant
(天辉)和Dire
(夜魇)。然后,如果有n
个参议员,给定字符串的大小将是n
。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是"Radiant"
或"Dire"
。
2.首先贪心肯定是能想到的,禁止另一方议员的序号肯定越早越好。
由于每次要挑选最早投票的议员,所以可以用两个队列代表两个阵营来模拟。通过比较两个队列的队首元素序号,可以知道哪一方先投票,而先投票的那一方还能进行下一轮投票,可以让他的序号加上n(实现多轮,实现循环)放入队尾,等待下一轮(代码实现技巧),然后弹出队列,而后投票的一方要永远的弹出队列。最后有一方队列为空,答案就是另一方。
代码
class Solution {
public:string predictPartyVictory(string senate) {int n = senate.size();queue<int> queR;queue<int> queD;for (int i = 0; i < n; ++i) {if (senate[i] == 'R')queR.push(i);elsequeD.push(i);}while (!queR.empty() && !queD.empty()) {if (queR.front() < queD.front())queR.push(queR.front() + n);elsequeD.push(queD.front() + n);queR.pop();queD.pop();}return queR.empty() ? "Dire" : "Radiant";}
};
4. 346. 数据流中的移动平均值(简单)
346. 数据流中的移动平均值 - 力扣(LeetCode)
思想
1.给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算其所有整数的移动平均值。
实现 MovingAverage
类:
MovingAverage(int size)
用窗口大小size
初始化对象。double next(int val)
计算并返回数据流中最后size
个值的移动平均值。
2.维护一个定长滑动窗口,队首元素出,队尾元素进,典型的先进先出队列。
代码
class MovingAverage {
public:int n;int sum;queue<int> que;MovingAverage(int size) {n = size;sum = 0;}double next(int val) {que.push(val);sum += val;while (que.size() > n) {sum -= que.front();que.pop();}return 1.0 * sum / que.size();}
};/*** Your MovingAverage object will be instantiated and called as such:* MovingAverage* obj = new MovingAverage(size);* double param_1 = obj->next(val);*/
5. 362. 敲击计数器(中等)
362. 敲击计数器 - 力扣(LeetCode)
思想
1.设计一个敲击计数器,使它可以统计在过去 5
分钟内被敲击次数。(即过去 300
秒)
您的系统应该接受一个时间戳参数 timestamp
(单位为 秒 ),并且您可以假定对系统的调用是按时间顺序进行的(即 timestamp
是单调递增的)。几次撞击可能同时发生。
实现 HitCounter
类:
HitCounter()
初始化命中计数器系统。void hit(int timestamp)
记录在timestamp
( 单位为秒 )发生的一次命中。在同一个timestamp
中可能会出现几个点击。int getHits(int timestamp)
返回timestamp
在过去 5 分钟内(即过去300
秒)的命中次数。
2.跟[[八、队列#1. 933. 最近的请求次数(简单,学习)]]一模一样,只是要注意在pop()
前判断队列不为空
代码
class HitCounter {
public:queue<int> que;HitCounter() {}void hit(int timestamp) { que.push(timestamp); }int getHits(int timestamp) {while (!que.empty() && que.front() <= timestamp - 300)que.pop();return que.size();}
};/*** Your HitCounter object will be instantiated and called as such:* HitCounter* obj = new HitCounter();* obj->hit(timestamp);* int param_2 = obj->getHits(timestamp);*/
6. 379. 电话目录管理系统(中等,学习)
379. 电话目录管理系统 - 力扣(LeetCode)
思想
1.设计一个电话目录管理系统,一开始有 maxNumbers
个位置能够储存号码。系统应该存储号码,检查某个位置是否为空,并清空给定的位置。
实现 PhoneDirectory
类:
PhoneDirectory(int maxNumbers)
电话目录初始有maxNumbers
个可用位置。int get()
提供一个未分配给任何人的号码。如果没有可用号码则返回-1
。bool check(int number)
如果位置number
可用返回true
否则返回false
。void release(int number)
回收或释放位置number
。
2.这题要实现两个功能:- 获取一个可用号码,且位置不限,可以用一个队列储存可用号码,但是一个队列无法快速判断可用号码在不在队列里面
- 快速判断一个号码可不可用,用哈希表,这里用集合
set
实现
代码
class PhoneDirectory {
public:set<int> st;queue<int> que;PhoneDirectory(int maxNumbers) {for (int i = 0; i < maxNumbers; ++i) {st.insert(i);que.push(i);}}int get() {if (que.empty())return -1;int x = que.front();que.pop();st.erase(x);return x;}bool check(int number) {if (st.find(number) != st.end())return true;return false;}void release(int number) {if (st.find(number) == st.end()) {st.insert(number);que.push(number);}}
};/*** Your PhoneDirectory object will be instantiated and called as such:* PhoneDirectory* obj = new PhoneDirectory(maxNumbers);* int param_1 = obj->get();* bool param_2 = obj->check(number);* obj->release(number);*/
7. 1429. 第一个唯一数字(中等)
1429. 第一个唯一数字 - 力扣(LeetCode)
思想
1.给定一系列整数,插入一个队列中,找出队列中第一个唯一整数。
实现 FirstUnique
类:
FirstUnique(int[] nums)
用数组里的数字初始化队列。int showFirstUnique()
返回队列中的 第一个唯一 整数的值。如果没有唯一整数,返回 -1。(译者注:此方法不移除队列中的任何元素)void add(int value)
将 value 插入队列中。
2.这题也是实现两个功能:- 判断元素出现次数,即哈希表,用
map<int,int>
实现 - 获取第一个唯一整数的值,因为是第一个,所以想到用队列实现,但是队列每次都从头查询太耗时,所以可以边查询边删除,即若当前队首元素次数大于1,之后的查询次数只增不减,所以直接弹出队列。
代码
class FirstUnique {
public:map<int, int> mp;queue<int> que;FirstUnique(vector<int>& nums) {int n = nums.size();for (int i = 0; i < n; ++i) {if (!mp.count(nums[i]))que.push(nums[i]);++mp[nums[i]];}}int showFirstUnique() {int res = -1;while (!que.empty()) {int x = que.front();if (mp[x] == 1) {res = x;break;} elseque.pop();}return res;}void add(int value) {if (!mp.count(value))que.push(value);++mp[value];}
};/*** Your FirstUnique object will be instantiated and called as such:* FirstUnique* obj = new FirstUnique(nums);* int param_1 = obj->showFirstUnique();* obj->add(value);*/
二、设计
套路
题目描述
学习经验
1. 1670. 设计前后中队列(中等,学习)
1670. 设计前中后队列 - 力扣(LeetCode)
思想
1.请你设计一个队列,支持在前,中,后三个位置的 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]
。
2.这题难点在于中间值怎么插入和删除,如果要用个缓冲区先寄存前半部分元素,然后处理中间元素,最后返回前半部分,太慢了。学习两个双端队列写法。
两个双端队列,一个为前半部分元素,一个为后半部分元素,定义0<=deqR.size()-deqL.size()<=
(为了之后的操作有规律、有重复性、可讨论性)
接下来就只要分类deqR.size()==deqL.size()
和deqR.size()-deqL.size()==1
和的情况了。
deqR.size()==deqL.size()
如下图所示:
![[设计前后中队列.jpg]]
deqR.size()-deqL.size()==1
呈对偶形式
而处理deqL
和deqR
的操作可以封装在一个函数中,根据deqL.size()
和deqR.size()
决定操作
代码
1.缓冲区
class FrontMiddleBackQueue {
public:deque<int> deq;FrontMiddleBackQueue() {}void pushFront(int val) { deq.push_front(val); }void pushMiddle(int val) {int len = deq.size() / 2; // pushMiddle直接除以2vector<int> tmp;int id = 0;while (id < len) {tmp.push_back(deq.front());deq.pop_front();++id;}deq.push_front(val);while (!tmp.empty()) {deq.push_front(tmp.back());tmp.pop_back();}}void pushBack(int val) { deq.push_back(val); }int popFront() {if (deq.empty())return -1;int x = deq.front();deq.pop_front();return x;}int popMiddle() {if (deq.empty())return -1;int len = (deq.size() - 1) / 2; // popMiddle要减1然后除以2vector<int> tmp;int id = 0;while (id < len) {tmp.push_back(deq.front());deq.pop_front();++id;}int x = deq.front();deq.pop_front();while (!tmp.empty()) {deq.push_front(tmp.back());tmp.pop_back();}return x;}int popBack() {if (deq.empty())return -1;int x = deq.back();deq.pop_back();return x;}
};/*** 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();*/
2.两个双端队列
class FrontMiddleBackQueue {
public:deque<int> deqL;deque<int> deqR;void balance() {if (deqL.size() > deqR.size()) {deqR.push_front(deqL.back());deqL.pop_back();}if (deqR.size() - deqL.size() > 1) {deqL.push_back(deqR.front());deqR.pop_front();}}FrontMiddleBackQueue() {}void pushFront(int val) {deqL.push_front(val);balance();}void pushMiddle(int val) {if (deqL.size() == deqR.size())deqR.push_front(val);elsedeqL.push_back(val);}void pushBack(int val) {deqR.push_back(val);balance();}// 因为左边小于等于右边,所以弹出左边复杂一点,可能左边为空,要弹出右边的int popFront() {if (deqR.empty())return -1;int x;if (deqL.empty()) {x = deqR.front();deqR.pop_front();} else {x = deqL.front();deqL.pop_front();}balance();return x;}int popMiddle() {if (deqR.empty())return -1;int x;if (deqL.size() == deqR.size()) {x = deqL.back();deqL.pop_back();} else {x = deqR.front();deqR.pop_front();}return x;}int popBack() {if (deqR.empty())return -1;int x = deqR.back();deqR.pop_back();balance();return x;}
};/*** 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();*/