题目描述
给定一个双端队列,初始时队列为空。
你要对其进行 q 次操作,每次操作可能是以下三种之一:
L x,从队列的左端插入整数 x。
R x,从队列的右端插入整数 x。
? x,请你计算为了使已经处于队列中的整数 x 位于队列的最左端或最右端,至少需要从最左端或最右端弹出多少个数字。
保证操作 3 一定合法( ? x 中的 x 一定已经处于队列之中)。
每个数字最多被插入到队列中 1 次(队列中一定不会存在重复数字)。
注意,操作 3 只是询问最少需要弹出多少数字,不是真的要弹出它们,队列中的数字始终不会减少。
输入格式
第一行包含整数 q。
接下来 q 行,每行包含一个操作信息,格式如题所述。
输出格式
对于每个操作 3,输出一行,一个整数表示结果。
数据范围
对于 30% 的数据,1≤q≤30,1≤x≤30。
对于 100% 的数据,1≤q≤2×105,1≤x≤2×105。
保证至少包含一个操作 3,
保证操作 1 和 2 不会重复插入数字。
保证操作 3 不会询问队列中不存在的数字。
样例
输入样例1:
8
L 1
R 2
R 3
? 2
L 4
? 1
L 5
? 1
输出样例1:
1
1
2
输入样例2:
10
L 100
R 100000
R 123
L 101
? 123
L 10
R 115
? 100
R 110
? 115
输出样例2:
0
2
1
算法1
(暴力枚举) $O(log n)$
维护两个map
lm用来存储从左端插入整数的下标以及值
rm用来存储从右端插入整数的下标以及值
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include<map>
using namespace std;
const int N = 1e6 + 10;
int q;
int l[N],r[N];
int lp,rp;
map<int,int> lm,rm;
void insert_to_L(int x){
l[++lp]=x;
lm[x]=lp;
}
void insert_to_R(int x){
r[++rp]=x;
rm[x]=rp;
}
int query(int x){
int p=0;
int lf=0,rf=0; //标记p在左边还是右边
if(lm.count(x)){
p=lm[x];
lf=1;
}
else{
p=rm[x];
rf=1;
}
if(lf){
return min(lp-p,p+rp+1);
}
else{
return min(lp+p+1,rp-p);
}
}
int main() {
cin >> q;
lp=-1,rp=-1;
while (q--) {
char op[10];
int x;
cin >> op >> x;
if (op[0] == 'L') {
insert_to_L(x);
}
else if (op[0] == 'R') {
insert_to_R(x);
}
else if (op[0] == '?') {
cout<<query(x)<<endl;
}
}
return 0;
}
算法2
(数组模拟双端队列) $O(n)$
通过观察算法1我们可以发现其实不用维护两个map和两个数组,只需一维数组就可以模拟
我们定义数组 p[x]=y 表示 插入整数x对映双端队列的下标
其中 插入左边的整数下标从-1,-2....到-l(l为插入左边整数的个数)
插入右边的整数下标从 0,1,2,…到r-1(r为插入右边整数的个数)
下图为模拟样例1:
![$V@3EHUHZP55L{BI~NP__QK.png](https://cdn.acwing.com/media/article/image/2021/05/29/88540_440ba1d0c0-$V@3EHUHZP55L{BI~NP__QK.png)
时间复杂度
每次插入就是给数组赋值 O(1)
每次查询即求差值的较小值 O(1)
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int p[N];
int n;
int main(){
cin>>n;
int l=0,r=-1;
while(n--){
char c;
int x;
cin>>c>>x;
if(c=='L') p[x]=--l;
else if(c=='R') p[x]=++r;
else printf("%d\n",min(p[x]-l,r-p[x]));
}
return 0;
}