题意很简单,给一个最大为50w长度的字符串,字符串只由ACGT四种字符组成,求修改一段最短的连续子串使得ACGT各占整个字符串的四分之一。
50w的长度很明显要用O(n^2)以下的复杂度,我的想法是O(n)的如下:
字符串长度为len,单个字符的个数限制为limit=len/4
需要修改的最短连续字符串要么是在最左端 要么是右端 要么是中间
1、首先先从右端遍历字符串,一次把每位的字符加入统计,每种字符串的数量必须小于4,一直读到满足限制条件为止,这时已经判断了需要修改的子串在左端的情况
2、然后继续从左端开始遍历字符串,同样把美味的字符加入统计判断是否满足限制条件,记录两端的位置。
3、接下来是重点:让左端前进一个字符的方法是让右端吐出来一个和左端字符相同的字符,因此让右端一直回退到和左端将要吃进去字符相同,并把这个字符也吐出来,然后左端一直前进到满足限制条件,连续做回退直到右端完全退出来,这样就判断了子串在中间和右端的情况
每次处理出一组情况就比较一下要处理的子串长度,最后的结果就是最短子串长度
代码写的比较丑,边界处理的有些问题
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int len;
int maxv;
char s[505000];
int cnt[27]={0};
int leftv;
int rightv;
int ans;
bool judge()
{
if(cnt['A'-'A']<=maxv&&cnt['C'-'A']<=maxv&&cnt['G'-'A']<=maxv&&cnt['T'-'A']<=maxv){
return true;
}
return false;
}
int main()
{
scanf("%d",&len);
cin>>s;
maxv=len/4;
ans=len;
leftv=0;
rightv=len-1;
while(1){
if(rightv>=0){
cnt[s[rightv]-'A']++;
}
else{
break;
}
if(judge()==true){
rightv--;
}
else{
cnt[s[rightv]-'A']--;
rightv++;
//cout<<rightv<<endl;
break;
}
}
rightv=max(0,rightv);
//cout<<cnt[0]<<" "<<cnt[2]<<" "<<cnt['G'-'A']<<" "<<cnt['T'-'A']<<" "<<endl;
int lmax=len-rightv;
int l=lmax;
//cout<<lmax<<endl;
if(lmax!=0){
while(1){
while(1){
cnt[s[leftv]-'A']++;
if(judge()==true&&leftv<len){
l++;
leftv++;
}
else{
cnt[s[leftv]-'A']--;
break;
}
}
lmax=max(l,lmax);
//cout<<lmax<<endl;
if(judge()==false||rightv>=len){
break;
}
while(s[leftv]!=s[rightv]&&rightv<len){
cnt[s[rightv]-'A']--;
rightv++;
l--;
}
if(rightv<len){
cnt[s[rightv]-'A']--;
rightv++;
l--;
}
}
}
ans=len-lmax;
printf("%d\n",ans);
return 0;
}