说明
徐老师最近做了一道题:求每个字母最多只出现一次的最长子串
他觉得这道题还不够有趣,于是他想到一个新题目:
对于一个字符串,求每个字母出现次数为偶数的最长子串
现在他用这道题来考考你,请找出这个最长子串的长度,并输出这个子串(如果有多个子串的长度相同,请输出从左往右最早出现的那一个)
输入格式
输入一行字符串 SS,保证只包含小写字母
对于 20% 的数据,1 <=|S|∣≤600
对于 30% 的数据,1<=ISI<=10000
对于 100% 的数据,1 ∣S∣≤200000
输出格式
输出第一行一个整数,表示最长的子串长度
输出第二行表示这个字符串的下标 [l,r][l,r]
样例
输入数据 1
bdaaadabd
Copy
输出数据 1
8
0 7
=========================================================================
这道题有三种做法,时间复杂度依次降低
一,暴力解法
直接三重循环,第一层for枚举起始位置,第二层for枚举结束位置,第三层for枚举26个字母,第4层for循环枚举[i,j]的区间内查看第三层循环中的字母出现了几次,然后在第三层循环中看次数是否为偶数,如果是偶数,则看区间大小是否>先前记录的长度即可,
时间复杂度:O(n^3)。可以说:前20%的数据就是为了暴力解法可以骗到分而设置的。
二,前缀和优化
如图所示,我们可以用一个二维数组来记录26个字母出现的次数,这样,我们就可以用这种预处理的方法把判断一个区间内是否为偶数的那个循环去掉。
方法:(mp[j][t] - mp[i - 1][t])
代码:
#include <bits/stdc++.h>
using namespace std;
char a[200010];
int len,m,l,sta,en,mp[200010][30];
int main()
{
cin>>a;
int l = strlen(a);//长度
for(int i = l; i > 0; i--) a[i] = a[i - 1];//让a数组从1的下标开始
for(int i = 1; i <= l; i++)
{
for(int j = 1; j < 27; j++) mp[i][j] = mp[i - 1][j];
mp[i][a[i] - 'a' + 1] = 1 + mp[i - 1][a[i] - 'a' + 1];//预处理前缀和
}
/*for(int i = 1; i <= a.size(); i++)
{
for(int j = 1; j < 27; j++)
cout<<mp[i][j]<<" ";
cout<<endl;
}*/
for(int i = 1; i <= l; i++)//起始点
for(int j = i + 1; j <= l; j++)//结束位置
{
m = 0;
for(int t = 1; t < 27; t++)
if((mp[j][t] - mp[i - 1][t]) % 2 == 1)//不为偶数
m = 1;//就标记
if(m == 0 && len < (j - i + 1))//如没被标记,并且当前的长度>以前的长度
{
len = j - i + 1;
sta = i;
en = j;
}
}
if(len == 0) sta = 1,en = 1;//特判
cout<<len<<endl<<sta - 1<<" "<<en - 1;
return 0;
}
时间复杂度:O(n^2),前30%的数据可以过了
三,简单数学知识+map优化
#include<bits/stdc++.h>
using namespace std;
map<int, int> mp;
char s[200000];
int main()
{
cin>>s;
int n = strlen(s),now = 0,ans = 0,l,r;
for (int i = 0; i < n; ++i)
{
now ^= 1 << (s[i] - 'a');
int pos = mp[now];
if (pos != 0 || now == 0)
{
if (i + 1 - pos > ans)
{
ans = i + 1 - pos;
l = pos;
r = i;
}
}
else mp[now] = i + 1;
}
cout<<ans<<endl<<l<<" "<<r;
return 0;
}
时间复杂度:O(n)!