题目描述
难度分:1200
输入T(≤2×104)表示T组数据。所有数据的字符串长度之和≤2×105。
每组数据输入一个长度≤2×105的字符串s,只包含数字1、2、3。
输出s中的最短子串的长度,该子串必须包含所有1、2、3三种字符。
如果没有这样的子串,输出0。
输入样例
7
123
12222133333332
112233
332211
12121212
333333
31121
输出样例
3
3
4
4
0
0
4
算法
二分答案
比较容易发现单调性,如果一个长度为len的子串包含1,2,3三种数字,那么更长的子串肯定也可以包含这三种数字,因此可以进行二分答案。对于一个给定的子串长度len,对s串进行滑动窗口,用一个长度为3的counter数组来维护子数组中1、2、3三种数的频数,有以下两种情况:
- 如果存在长度为len的子串满足条件,记录答案,并往左搜索看能不能使长度更短。
- 否则说明当前长度给得太苛刻了,往右搜索更大的长度。
复杂度分析
时间复杂度
二分答案的下界是3,上界是n,每次check就是一个O(n)的滑动窗口,因此算法整体的时间复杂度为O(nlog2n)。
空间复杂度
除了输入的字符串s,仅使用了有限几个变量,因此算法的额外空间复杂度为O(1)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int T, n;
char s[N];
bool check(int len) {
int counter[3] = {0};
for(int i = 1; i <= len; i++) {
counter[s[i] - '1']++;
}
if(counter[0] && counter[1] && counter[2]) return true;
for(int i = len + 1; i <= n; i++) {
int j = i - len;
counter[s[i] - '1']++;
counter[s[j] - '1']--;
if(counter[0] && counter[1] && counter[2]) return true;
}
return false;
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%s", s + 1);
n = strlen(s + 1);
int left = 3, right = n, ans = 0;
while(left <= right) {
int mid = left + right >> 1;
if(check(mid)) {
ans = mid;
right = mid - 1;
}else {
left = mid + 1;
}
}
printf("%d\n", ans);
}
return 0;
}