题目描述
难度分:1088
输入n,m(1≤m≤n≤1.5×106)和长为n的数组a(0≤a[i]<n)。
定义mex(b)为不在数组b中的最小非负整数。
遍历a的所有长为m的连续子数组b,输出mex(b)的最小值。
输入样例1
3 2
0 0 1
输出样例1
1
输入样例2
3 2
1 1 1
输出样例2
0
输入样例3
3 2
0 1 0
输出样例3
2
输入样例4
7 3
0 0 1 2 0 1 0
输出样例4
2
算法
滑动窗口
假如子数组[i,i+m)的Mex值为x,此时如果窗口要滑到[i+1,i+m],那就会删掉一个left=Ai,加入一个right=Ai+m。有如下两种情况:
- 如果此时窗口内不再有left,需要考虑Mex值的更新:(1) left<x,则Mex会减小为left。(2) 否则Mex值仍然是x,保持不变。因为left不可能等于x,否则前一个窗口就含有x,与Mex=x矛盾。所以left>x,而把一个更大的数删掉是不会影响Mex值的。
- 否则等于说又加进来了一个新数,也需要考虑Mex值的更新:(1) 如果right=x,刚好补齐了x这个空缺,而窗口内一共就m个数,所以Mex=x+1。(2) 如果right<x,因为Mex=x,所以小于x的数是本来就有的,Mex=x不变。(3) 如果right>x,加进来一个更大的数不影响Mex的值,Mex=x仍然不变。
复杂度分析
时间复杂度
在滑动窗口的过程中每次更新窗口和Mex值都是O(1)的,因此算法整体的时间复杂度是O(n)。
空间复杂度
使用了一个哈希表来存储长度为m的子数组中的元素,因此在最差的情况下,其中可能有m个键值对,额外空间复杂度为O(m)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 1500010;
int a[N], m, n;
int main() {
scanf("%d%d", &n, &m);
unordered_map<int, int> counter;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(i <= m) {
counter[a[i]]++;
}
}
int mex = 0;
while(counter.find(mex) != counter.end()) mex++;
int ans = mex;
for(int r = m + 1; r <= n; r++) {
int left = a[r - m], right = a[r];
--counter[left], ++counter[right];
if(counter[left] == 0) {
counter.erase(left);
if(left < mex) mex = left;
}else {
if(right == mex) mex++;
}
ans = min(ans, mex);
}
printf("%d\n", ans);
return 0;
}